Improve performance of app - 1.182 🚀

The App.tsx had a bunch of separate useState calls which would
cause unneccesary re-renders of the entire app. This became
apparent after adding the global progress bar, since it caused
even more re-renders to the point of being unusable.
This commit is contained in:
Brandon Presley 2023-11-13 17:33:32 +13:00
parent 49646c3107
commit 6950cd04f4
7 changed files with 115 additions and 87 deletions

114
App.tsx
View File

@ -1,27 +1,22 @@
import {
NavigationContainer,
DarkTheme as NavigationDarkTheme,
DefaultTheme as NavigationDefaultTheme,
NavigationContainer,
} from "@react-navigation/native";
import React, { useEffect, useMemo, useState } from "react";
import { useColorScheme } from "react-native";
import {
MD3DarkTheme as PaperDarkTheme,
MD3LightTheme as PaperDefaultTheme,
ProgressBar,
Provider as PaperProvider,
Snackbar,
} from "react-native-paper";
import MaterialIcon from "react-native-vector-icons/MaterialCommunityIcons";
import AppSnack from "./AppSnack";
import AppStack from "./AppStack";
import TimerProgress from "./TimerProgress";
import { AppDataSource } from "./data-source";
import { settingsRepo } from "./db";
import { emitter } from "./emitter";
import { TickEvent } from "./TimerPage";
import { TOAST } from "./toast";
import { ThemeContext } from "./use-theme";
import Settings from "./settings";
import { MARGIN } from "./constants";
export const CombinedDefaultTheme = {
...NavigationDefaultTheme,
@ -43,61 +38,54 @@ export const CombinedDarkTheme = {
const App = () => {
const phoneTheme = useColorScheme();
const [initialized, setInitialized] = useState(false);
const [snackbar, setSnackbar] = useState("");
const [appTheme, setAppTheme] = useState("system");
const [progress, setProgress] = useState(0);
const [settings, setSettings] = useState<Settings>();
const [lightColor, setLightColor] = useState<string>(
CombinedDefaultTheme.colors.primary
);
const [appSettings, setAppSettings] = useState({
startup: undefined,
theme: "system",
lightColor: CombinedDefaultTheme.colors.primary,
darkColor: CombinedDarkTheme.colors.primary,
});
const [darkColor, setDarkColor] = useState<string>(
CombinedDarkTheme.colors.primary
);
console.log("Rerendered App");
useEffect(() => {
(async () => {
if (!AppDataSource.isInitialized) await AppDataSource.initialize();
const gotSettings = await settingsRepo.findOne({ where: {} });
console.log({ gotSettings });
setSettings(gotSettings);
setAppTheme(gotSettings.theme);
if (gotSettings.lightColor) setLightColor(gotSettings.lightColor);
if (gotSettings.darkColor) setDarkColor(gotSettings.darkColor);
setInitialized(true);
setAppSettings({
startup: gotSettings.startup,
theme: gotSettings.theme,
lightColor: gotSettings.lightColor,
darkColor: gotSettings.darkColor,
});
})();
const descriptions = [
emitter.addListener(TOAST, ({ value }: { value: string }) => {
setSnackbar(value);
}),
emitter.addListener("tick", (event: TickEvent) => {
setProgress((Number(event.minutes) * 60 + Number(event.seconds)) / 210);
}),
];
return () => descriptions.forEach((description) => description.remove());
}, []);
const paperTheme = useMemo(() => {
const darkTheme = lightColor
const darkTheme = appSettings.lightColor
? {
...CombinedDarkTheme,
colors: { ...CombinedDarkTheme.colors, primary: darkColor },
colors: {
...CombinedDarkTheme.colors,
primary: appSettings.darkColor,
},
}
: CombinedDarkTheme;
const lightTheme = lightColor
const lightTheme = appSettings.lightColor
? {
...CombinedDefaultTheme,
colors: { ...CombinedDefaultTheme.colors, primary: lightColor },
colors: {
...CombinedDefaultTheme.colors,
primary: appSettings.lightColor,
},
}
: CombinedDefaultTheme;
let value = phoneTheme === "dark" ? darkTheme : lightTheme;
if (appTheme === "dark") value = darkTheme;
else if (appTheme === "light") value = lightTheme;
if (appSettings.theme === "dark") value = darkTheme;
else if (appSettings.theme === "light") value = lightTheme;
return value;
}, [phoneTheme, appTheme, lightColor, darkColor]);
}, [phoneTheme, appSettings]);
return (
<PaperProvider
@ -105,46 +93,26 @@ const App = () => {
settings={{ icon: (props) => <MaterialIcon {...props} /> }}
>
<NavigationContainer theme={paperTheme}>
{initialized && (
{appSettings.startup !== undefined && (
<ThemeContext.Provider
value={{
theme: appTheme,
setTheme: setAppTheme,
lightColor,
setLightColor,
darkColor,
setDarkColor,
theme: appSettings.theme,
setTheme: (theme) => setAppSettings({ ...appSettings, theme }),
lightColor: appSettings.lightColor,
setLightColor: (color) =>
setAppSettings({ ...appSettings, lightColor: color }),
darkColor: appSettings.darkColor,
setDarkColor: (color) =>
setAppSettings({ ...appSettings, darkColor: color }),
}}
>
<AppStack settings={settings} />
<AppStack startup={appSettings.startup} />
</ThemeContext.Provider>
)}
</NavigationContainer>
<Snackbar
duration={3000}
onDismiss={() => setSnackbar("")}
visible={!!snackbar}
action={{
label: "Close",
onPress: () => setSnackbar(""),
textColor: paperTheme.colors.background,
}}
>
{snackbar}
</Snackbar>
{progress > 0 && (
<ProgressBar
style={{
position: "absolute",
bottom: MARGIN / 2,
left: MARGIN,
right: MARGIN,
}}
progress={progress}
/>
)}
<AppSnack textColor={paperTheme.colors.background} />
<TimerProgress />
</PaperProvider>
);
};

View File

@ -1,6 +1,6 @@
import { createDrawerNavigator } from "@react-navigation/drawer";
import { StackScreenProps } from "@react-navigation/stack";
import { IconButton } from "react-native-paper";
import { DrawerParams } from "./drawer-param-list";
import ExerciseList from "./ExerciseList";
import GraphsList from "./GraphsList";
import InsightsPage from "./InsightsPage";
@ -8,20 +8,19 @@ import PlanList from "./PlanList";
import SetList from "./SetList";
import SettingsPage from "./SettingsPage";
import TimerPage from "./TimerPage";
import useDark from "./use-dark";
import WeightList from "./WeightList";
import Settings from "./settings";
import { StackScreenProps } from "@react-navigation/stack";
import { DrawerParams } from "./drawer-param-list";
import useDark from "./use-dark";
const Drawer = createDrawerNavigator<DrawerParams>();
interface AppDrawerParams {
settings: Settings;
startup: string;
}
export default function AppDrawer({
route,
}: StackScreenProps<{ settings: AppDrawerParams }>) {
}: StackScreenProps<{ startup: AppDrawerParams }>) {
const dark = useDark();
return (
@ -31,9 +30,7 @@ export default function AppDrawer({
swipeEdgeWidth: 1000,
headerShown: false,
}}
initialRouteName={
(route.params.settings.startup || "Home") as keyof DrawerParams
}
initialRouteName={(route.params.startup || "Home") as keyof DrawerParams}
>
<Drawer.Screen
name="Home"

33
AppSnack.tsx Normal file
View File

@ -0,0 +1,33 @@
import React, { useEffect, useState } from "react";
import { Snackbar } from "react-native-paper";
import { emitter } from "./emitter";
import { TOAST } from "./toast";
export default function AppSnack({ textColor }: { textColor: string }) {
const [snackbar, setSnackbar] = useState("");
useEffect(() => {
const description = emitter.addListener(
TOAST,
({ value }: { value: string }) => {
setSnackbar(value);
}
);
return description.remove;
}, []);
return (
<Snackbar
duration={3000}
onDismiss={() => setSnackbar("")}
visible={!!snackbar}
action={{
label: "Close",
onPress: () => setSnackbar(""),
textColor,
}}
>
{snackbar}
</Snackbar>
);
}

View File

@ -50,7 +50,7 @@ export type StackParams = {
const Stack = createStackNavigator<StackParams>();
export default function AppStack({ settings }: { settings: Settings }) {
export default function AppStack({ startup }: { startup: string }) {
return (
<Stack.Navigator
screenOptions={{ headerShown: false, animationEnabled: false }}
@ -58,7 +58,7 @@ export default function AppStack({ settings }: { settings: Settings }) {
<Stack.Screen
name="Drawer"
component={AppDrawer}
initialParams={{ settings }}
initialParams={{ startup }}
/>
<Stack.Screen name="EditSet" component={EditSet} />
<Stack.Screen name="EditSets" component={EditSets} />

30
TimerProgress.tsx Normal file
View File

@ -0,0 +1,30 @@
import { useEffect, useState } from "react";
import { emitter } from "./emitter";
import { TickEvent } from "./TimerPage";
import { ProgressBar } from "react-native-paper";
export default function TimerProgress() {
const [progress, setProgress] = useState(0);
useEffect(() => {
const description = emitter.addListener(
"tick",
({ minutes, seconds }: TickEvent) => {
setProgress((Number(minutes) * 60 + Number(seconds)) / 210);
}
);
return description.remove;
}, []);
if (progress === 0) return null;
return (
<ProgressBar
style={{
position: "absolute",
bottom: 0,
}}
progress={progress}
/>
);
}

View File

@ -85,8 +85,8 @@ android {
applicationId "com.massive"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 36207
versionName "1.181"
versionCode 36208
versionName "1.182"
}
signingConfigs {
release {

View File

@ -1,6 +1,6 @@
{
"name": "massive",
"version": "1.181",
"version": "1.182",
"private": true,
"license": "GPL-3.0-only",
"scripts": {