mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-05-02 20:16:59 +00:00
Remove useMemo/useCallback across the web UI
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { type FC, type PropsWithChildren, memo, useMemo } from 'react';
|
||||
import { type FC, type PropsWithChildren, memo } from 'react';
|
||||
|
||||
import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined';
|
||||
import ErrorIcon from '@mui/icons-material/Error';
|
||||
@@ -38,18 +38,17 @@ const MessageBox: FC<PropsWithChildren<MessageBoxProps>> = ({
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const { Icon, backgroundColor } = useMemo(() => {
|
||||
const Icon = LEVEL_ICONS[level];
|
||||
const palettePath = LEVEL_PALETTE_PATHS[level];
|
||||
const [key, shade] = palettePath.split('.') as [
|
||||
keyof typeof theme.palette,
|
||||
string
|
||||
];
|
||||
const paletteKey = theme.palette[key] as unknown as Record<string, string>;
|
||||
const backgroundColor = paletteKey[shade];
|
||||
|
||||
return { Icon, backgroundColor };
|
||||
}, [level, theme]);
|
||||
const Icon = LEVEL_ICONS[level];
|
||||
const palettePath = LEVEL_PALETTE_PATHS[level];
|
||||
const [paletteKeyName, shade] = palettePath.split('.') as [
|
||||
keyof typeof theme.palette,
|
||||
string
|
||||
];
|
||||
const paletteKey = theme.palette[paletteKeyName] as unknown as Record<
|
||||
string,
|
||||
string
|
||||
>;
|
||||
const backgroundColor = paletteKey[shade];
|
||||
|
||||
return (
|
||||
<Box
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo, useCallback, useContext, useMemo } from 'react';
|
||||
import { memo, useContext } from 'react';
|
||||
import type { ChangeEventHandler } from 'react';
|
||||
import type { CSSProperties } from 'react';
|
||||
|
||||
@@ -44,27 +44,14 @@ const LANGUAGE_OPTIONS: LanguageOption[] = [
|
||||
const LanguageSelector = () => {
|
||||
const { setLocale, locale, LL } = useContext(I18nContext);
|
||||
|
||||
const onLocaleSelected: ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||
async ({ target }) => {
|
||||
const loc = target.value as Locales;
|
||||
localStorage.setItem('lang', loc);
|
||||
await loadLocaleAsync(loc);
|
||||
setLocale(loc);
|
||||
},
|
||||
[setLocale]
|
||||
);
|
||||
|
||||
// Memoize menu items to prevent recreation on every render
|
||||
const menuItems = useMemo(
|
||||
() =>
|
||||
LANGUAGE_OPTIONS.map(({ key, flag, label }) => (
|
||||
<MenuItem key={key} value={key}>
|
||||
<img src={flag} style={flagStyle} alt={label} />
|
||||
{label}
|
||||
</MenuItem>
|
||||
)),
|
||||
[]
|
||||
);
|
||||
const onLocaleSelected: ChangeEventHandler<HTMLInputElement> = async ({
|
||||
target
|
||||
}) => {
|
||||
const loc = target.value as Locales;
|
||||
localStorage.setItem('lang', loc);
|
||||
await loadLocaleAsync(loc);
|
||||
setLocale(loc);
|
||||
};
|
||||
|
||||
return (
|
||||
<TextField
|
||||
@@ -76,7 +63,12 @@ const LanguageSelector = () => {
|
||||
size="small"
|
||||
select
|
||||
>
|
||||
{menuItems}
|
||||
{LANGUAGE_OPTIONS.map(({ key, flag, label }) => (
|
||||
<MenuItem key={key} value={key}>
|
||||
<img src={flag} style={flagStyle} alt={label} />
|
||||
{label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo, useCallback, useState } from 'react';
|
||||
import { memo, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||
@@ -13,9 +13,9 @@ type ValidatedPasswordFieldProps = Omit<ValidatedTextFieldProps, 'type'>;
|
||||
const ValidatedPasswordField: FC<ValidatedPasswordFieldProps> = ({ ...props }) => {
|
||||
const [showPassword, setShowPassword] = useState<boolean>(false);
|
||||
|
||||
const togglePasswordVisibility = useCallback(() => {
|
||||
const togglePasswordVisibility = () => {
|
||||
setShowPassword((prev) => !prev);
|
||||
}, []);
|
||||
};
|
||||
|
||||
return (
|
||||
<ValidatedTextField
|
||||
|
||||
@@ -18,7 +18,6 @@ const LayoutComponent: FC<RequiredChildrenProps> = ({ children }) => {
|
||||
const [title, setTitle] = useState(PROJECT_NAME);
|
||||
const { pathname } = useLocation();
|
||||
|
||||
// Memoize drawer toggle handler to prevent unnecessary re-renders
|
||||
const handleDrawerToggle = useCallback(() => {
|
||||
setMobileOpen((prev) => !prev);
|
||||
}, []);
|
||||
@@ -28,7 +27,6 @@ const LayoutComponent: FC<RequiredChildrenProps> = ({ children }) => {
|
||||
setMobileOpen(false);
|
||||
}, [pathname]);
|
||||
|
||||
// Memoize context value to prevent unnecessary re-renders
|
||||
const contextValue = useMemo(() => ({ title, setTitle }), [title]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { Link, useLocation, useNavigate } from 'react-router';
|
||||
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
@@ -39,14 +39,11 @@ const LayoutAppBarComponent = ({ title, onToggleDrawer }: LayoutAppBarProps) =>
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
const pathnames = useMemo(
|
||||
() => location.pathname.split('/').filter((x) => x),
|
||||
[location.pathname]
|
||||
);
|
||||
const pathnames = location.pathname.split('/').filter((x) => x);
|
||||
|
||||
const handleBackClick = useCallback(() => {
|
||||
const handleBackClick = () => {
|
||||
void navigate('/' + pathnames[0]);
|
||||
}, [navigate, pathnames]);
|
||||
};
|
||||
|
||||
return (
|
||||
<AppBar position="fixed" sx={appBarStyles}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo, useMemo } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { Box, Divider, Drawer, Toolbar, Typography, styled } from '@mui/material';
|
||||
|
||||
@@ -24,22 +24,18 @@ interface LayoutDrawerProps {
|
||||
}
|
||||
|
||||
const LayoutDrawerComponent = ({ mobileOpen, onClose }: LayoutDrawerProps) => {
|
||||
// Memoize drawer content to prevent unnecessary re-renders
|
||||
const drawer = useMemo(
|
||||
() => (
|
||||
<>
|
||||
<Toolbar disableGutters>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', p: 2 }}>
|
||||
<LayoutDrawerLogo src="/app/icon.png" alt={PROJECT_NAME} />
|
||||
<Typography variant="h6">{PROJECT_NAME}</Typography>
|
||||
</Box>
|
||||
<Divider absolute />
|
||||
</Toolbar>
|
||||
<Divider />
|
||||
<LayoutMenu />
|
||||
</>
|
||||
),
|
||||
[]
|
||||
const drawer = (
|
||||
<>
|
||||
<Toolbar disableGutters>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', p: 2 }}>
|
||||
<LayoutDrawerLogo src="/app/icon.png" alt={PROJECT_NAME} />
|
||||
<Typography variant="h6">{PROJECT_NAME}</Typography>
|
||||
</Box>
|
||||
<Divider absolute />
|
||||
</Toolbar>
|
||||
<Divider />
|
||||
<LayoutMenu />
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo, useCallback, useContext, useState } from 'react';
|
||||
import { memo, useContext, useState } from 'react';
|
||||
|
||||
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
|
||||
import AssessmentIcon from '@mui/icons-material/Assessment';
|
||||
@@ -22,9 +22,9 @@ const LayoutMenuComponent = () => {
|
||||
const { LL } = useI18nContext();
|
||||
const [menuOpen, setMenuOpen] = useState(true);
|
||||
|
||||
const handleMenuToggle = useCallback(() => {
|
||||
const handleMenuToggle = () => {
|
||||
setMenuOpen((prev) => !prev);
|
||||
}, []);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo, useMemo } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { Link, useLocation } from 'react-router';
|
||||
|
||||
import { ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
|
||||
@@ -21,50 +21,40 @@ const LayoutMenuItemComponent = ({
|
||||
}: LayoutMenuItemProps) => {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const selected = useMemo(() => routeMatches(to, pathname), [to, pathname]);
|
||||
const selected = routeMatches(to, pathname);
|
||||
|
||||
// Memoize dynamic styles based on selected state
|
||||
const buttonStyles: SxProps<Theme> = useMemo(
|
||||
() => ({
|
||||
transition: 'all 0.05s cubic-bezier(0.55, 0.085, 0.68, 0.53)',
|
||||
backgroundColor: selected ? 'rgba(144, 202, 249, 0.1)' : 'transparent',
|
||||
borderRadius: '8px',
|
||||
margin: '2px 8px',
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(68, 82, 211, 0.39)'
|
||||
},
|
||||
'&::before': {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: selected ? '3px' : '0px',
|
||||
backgroundColor: '#90caf9',
|
||||
transition: 'width 0.05s cubic-bezier(0.55, 0.085, 0.68, 0.53)'
|
||||
}
|
||||
}),
|
||||
[selected]
|
||||
);
|
||||
const buttonStyles: SxProps<Theme> = {
|
||||
transition: 'all 0.05s cubic-bezier(0.55, 0.085, 0.68, 0.53)',
|
||||
backgroundColor: selected ? 'rgba(144, 202, 249, 0.1)' : 'transparent',
|
||||
borderRadius: '8px',
|
||||
margin: '2px 8px',
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(68, 82, 211, 0.39)'
|
||||
},
|
||||
'&::before': {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: selected ? '3px' : '0px',
|
||||
backgroundColor: '#90caf9',
|
||||
transition: 'width 0.05s cubic-bezier(0.55, 0.085, 0.68, 0.53)'
|
||||
}
|
||||
};
|
||||
|
||||
const iconStyles: SxProps<Theme> = useMemo(
|
||||
() => ({
|
||||
color: selected ? '#90caf9' : '#9e9e9e',
|
||||
transition: 'color 0.05s cubic-bezier(0.55, 0.085, 0.68, 0.53)',
|
||||
transform: selected ? 'scale(1.1)' : 'scale(1)',
|
||||
transitionProperty: 'color, transform'
|
||||
}),
|
||||
[selected]
|
||||
);
|
||||
const iconStyles: SxProps<Theme> = {
|
||||
color: selected ? '#90caf9' : '#9e9e9e',
|
||||
transition: 'color 0.05s cubic-bezier(0.55, 0.085, 0.68, 0.53)',
|
||||
transform: selected ? 'scale(1.1)' : 'scale(1)',
|
||||
transitionProperty: 'color, transform'
|
||||
};
|
||||
|
||||
const textStyles: SxProps<Theme> = useMemo(
|
||||
() => ({
|
||||
color: selected ? '#90caf9' : '#f5f5f5',
|
||||
transition: 'color 0.05s cubic-bezier(0.55, 0.085, 0.68, 0.53)',
|
||||
transitionProperty: 'color, font-weight'
|
||||
}),
|
||||
[selected]
|
||||
);
|
||||
const textStyles: SxProps<Theme> = {
|
||||
color: selected ? '#90caf9' : '#f5f5f5',
|
||||
transition: 'color 0.05s cubic-bezier(0.55, 0.085, 0.68, 0.53)',
|
||||
transitionProperty: 'color, font-weight'
|
||||
};
|
||||
|
||||
return (
|
||||
<ListItemButton
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo, useCallback } from 'react';
|
||||
import { memo } from 'react';
|
||||
import type { Blocker } from 'react-router';
|
||||
|
||||
import {
|
||||
@@ -15,13 +15,13 @@ import { useI18nContext } from 'i18n/i18n-react';
|
||||
const BlockNavigation = ({ blocker }: { blocker: Blocker }) => {
|
||||
const { LL } = useI18nContext();
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
const handleReset = () => {
|
||||
blocker.reset?.();
|
||||
}, [blocker]);
|
||||
};
|
||||
|
||||
const handleProceed = useCallback(() => {
|
||||
const handleProceed = () => {
|
||||
blocker.proceed?.();
|
||||
}, [blocker]);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog sx={dialogStyle} open={blocker.state === 'blocked'}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo, useCallback } from 'react';
|
||||
import { memo } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
@@ -16,12 +16,9 @@ const RouterTabs: FC<RouterTabsProps> = ({ value, children }) => {
|
||||
const theme = useTheme();
|
||||
const smallDown = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
const handleTabChange = useCallback(
|
||||
(_event: unknown, path: string) => {
|
||||
void navigate(path);
|
||||
},
|
||||
[navigate]
|
||||
);
|
||||
const handleTabChange = (_event: unknown, path: string) => {
|
||||
void navigate(path);
|
||||
};
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
|
||||
Reference in New Issue
Block a user