Skip to content

Commit

Permalink
refactor: removed unnecessary re-renders (#116)
Browse files Browse the repository at this point in the history
giuliano176 authored Aug 26, 2022

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent cee27aa commit 5531f4c
Showing 10 changed files with 156 additions and 104 deletions.
24 changes: 14 additions & 10 deletions src/search/module-selector.tsx
Original file line number Diff line number Diff line change
@@ -6,24 +6,21 @@
import React, { FC, useEffect, useMemo, useState } from 'react';
import styled from 'styled-components';
import { Container, Row, Text, Icon, Dropdown } from '@zextras/carbonio-design-system';
import { AppRoute } from '../../types';
import { useAppStore } from '../store/app';
import { useSearchStore } from './search-store';
import { SEARCH_APP_ID } from '../constants';
import { pushHistory } from '../history/hooks';
import { useCurrentRoute, pushHistory } from '../history/hooks';

const SelectorContainer = styled(Container)<{ open: boolean }>`
border-right: 1px solid ${({ theme }): string => theme.palette.gray4.regular};
cursor: 'pointer';
cursor: pointer;
background: ${({ theme, open }): string => theme.palette[open ? 'gray5' : 'gray6'].regular};
&:hover {
background: ${({ theme, open }): string => theme.palette[open ? 'gray5' : 'gray6'].hover};
}
`;

export const ModuleSelector: FC<{ activeRoute: AppRoute; disabled: boolean }> = ({
activeRoute
}) => {
const ModuleSelectorComponent: FC<{ app: string | undefined }> = ({ app }) => {
const modules = useAppStore((s) => s.views.search);
const { module, updateModule } = useSearchStore();
const fullModule = useMemo(
@@ -49,13 +46,13 @@ export const ModuleSelector: FC<{ activeRoute: AppRoute; disabled: boolean }> =
);

useEffect(() => {
if (activeRoute?.app !== SEARCH_APP_ID) {
if (!fullModule || fullModule?.app !== activeRoute?.app) {
updateModule((modules.find((m) => m.app === activeRoute?.app) ?? modules[0])?.route);
if (app !== SEARCH_APP_ID) {
if (!fullModule || fullModule?.app !== app) {
updateModule((modules.find((m) => m.app === app) ?? modules[0])?.route);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeRoute, modules, updateModule]);
}, [app, modules, updateModule]);

if (!fullModule) {
return null;
@@ -90,3 +87,10 @@ export const ModuleSelector: FC<{ activeRoute: AppRoute; disabled: boolean }> =
</Dropdown>
);
};

const MemoModuleSelector = React.memo(ModuleSelectorComponent);

export const ModuleSelector = (): JSX.Element => {
const activeRoute = useCurrentRoute();
return <MemoModuleSelector app={activeRoute?.app} />;
};
12 changes: 4 additions & 8 deletions src/search/search-bar.tsx
Original file line number Diff line number Diff line change
@@ -22,14 +22,14 @@ import { useLocalStorage } from '../shell/hooks';
import { SEARCH_APP_ID } from '../constants';

import { useSearchStore } from './search-store';
import { QueryChip, SearchBarProps } from '../../types';
import { QueryChip } from '../../types';
import { ModuleSelector } from './module-selector';

const OutlinedIconButton = styled(IconButton)`
border: 1px solid
${({ theme, disabled }): string =>
disabled ? theme.palette.primary.disabled : theme.palette.primary.regular};
display: 'block';
display: block;
& svg {
border: none;
}
@@ -59,11 +59,7 @@ type SearchLocalStorage = Array<{
app: string;
id: string;
}>;
export const SearchBar: FC<SearchBarProps> = ({
activeRoute
// primaryAction,
// secondaryActions
}) => {
export const SearchBar: FC = () => {
const [searchIsEnabled, setSearchIsEnabled] = useState(false);
const inputRef = useRef<HTMLInputElement>();
const [t] = useTranslation();
@@ -375,7 +371,7 @@ export const SearchBar: FC<SearchBarProps> = ({
<Container minWidth="512px" width="fill">
<Container orientation="horizontal" width="fill">
<Container width="fit">
<ModuleSelector activeRoute={activeRoute} disabled={searchDisabled} />
<ModuleSelector />
</Container>
<StyledContainer orientation="horizontal">
<StyledChipInput
66 changes: 38 additions & 28 deletions src/shell/creation-button.tsx
Original file line number Diff line number Diff line change
@@ -9,39 +9,18 @@ import { reduce, groupBy } from 'lodash';
import { MultiButton, Button, Dropdown } from '@zextras/carbonio-design-system';
import { useTranslation } from 'react-i18next';
import { useLocation } from 'react-router-dom';
import { Location } from 'history';
import { useActions } from '../store/integrations/hooks';
import { ACTION_TYPES } from '../constants';
import { Action, AppRoute } from '../../types';
import { useAppList } from '../store/app';
import { useCurrentRoute } from '../history/hooks';

const useSecondaryActions = (
actions: Array<Action>,
activeRoute?: AppRoute
): Array<Action | { type: string; id: string }> => {
const apps = useAppList();

const byApp = useMemo(() => groupBy(actions, 'group'), [actions]);
return useMemo(
() => [
...(byApp[activeRoute?.app ?? ''] ?? []),
...reduce(
apps,
(acc, app, i) => {
if (app.name !== activeRoute?.app && byApp[app.name]?.length > 0) {
acc.push({ type: 'divider', label: '', id: `divider-${i}` }, ...byApp[app.name]);
}
return acc;
},
[] as Array<Action | { type: string; id: string }>
)
],
[activeRoute?.app, apps, byApp]
);
};

export const CreationButton: FC<{ activeRoute?: AppRoute }> = ({ activeRoute }) => {
export const CreationButtonComponent: FC<{ activeRoute: AppRoute; location: Location }> = ({
activeRoute,
location
}) => {
const [t] = useTranslation();
const location = useLocation();
const actions = useActions({ activeRoute, location }, ACTION_TYPES.NEW);
const [open, setOpen] = useState(false);
const primaryAction = useMemo(
@@ -51,7 +30,22 @@ export const CreationButton: FC<{ activeRoute?: AppRoute }> = ({ activeRoute })
),
[actions, activeRoute?.app, activeRoute?.id]
);
const secondaryActions = useSecondaryActions(actions, activeRoute);
const apps = useAppList();
const byApp = useMemo(() => groupBy(actions, 'group'), [actions]);

const secondaryActions = [
...(byApp[activeRoute?.app ?? ''] ?? []),
...reduce(
apps,
(acc, app, i) => {
if (app.name !== activeRoute?.app && byApp[app.name]?.length > 0) {
acc.push({ type: 'divider', label: '', id: `divider-${i}` }, ...byApp[app.name]);
}
return acc;
},
[] as Array<Action | { type: string; id: string }>
)
];

const onClose = useCallback(() => {
setOpen(false);
@@ -80,3 +74,19 @@ export const CreationButton: FC<{ activeRoute?: AppRoute }> = ({ activeRoute })
</Dropdown>
);
};

const MemoCreationButton = React.memo(CreationButtonComponent);

export const CreationButton: FC = () => {
const locationFull = useLocation() as Location;
const activeRoute = useCurrentRoute() as AppRoute;

const truncateLocation = (location: Location): Location => ({
...location,
pathname: location?.pathname?.split('/').slice(0, 2).join('/'),
key: ''
});

const location = useMemo(() => truncateLocation(locationFull), [locationFull]);
return <MemoCreationButton activeRoute={activeRoute} location={location} />;
};
16 changes: 3 additions & 13 deletions src/shell/shell-header.tsx
Original file line number Diff line number Diff line change
@@ -19,13 +19,11 @@ import Logo from '../svg/carbonio.svg';
import { SearchBar } from '../search/search-bar';
import { CreationButton } from './creation-button';
import { useAppStore } from '../store/app';
import { AppRoute } from '../../types';

const ShellHeader: FC<{
activeRoute: AppRoute;
mobileNavIsOpen: boolean;
onMobileMenuClick: () => void;
}> = ({ activeRoute, mobileNavIsOpen, onMobileMenuClick, children }) => {
}> = ({ mobileNavIsOpen, onMobileMenuClick, children }) => {
const screenMode = useScreenMode();
const searchEnabled = useAppStore((s) => s.views.search.length > 0);
return (
@@ -58,17 +56,9 @@ const ShellHeader: FC<{
<Logo height="32px" />
</Container>
<Padding horizontal="large">
<CreationButton activeRoute={activeRoute} />
<CreationButton />
</Padding>
<Responsive mode="desktop">
{searchEnabled && (
<SearchBar
activeRoute={activeRoute}
// primaryAction={primaryAction}
// secondaryActions={secondaryActions}
/>
)}
</Responsive>
<Responsive mode="desktop">{searchEnabled && <SearchBar />}</Responsive>
</Container>
<Container orientation="horizontal" width="25%" mainAlignment="flex-end">
<Responsive mode="desktop">{children}</Responsive>
50 changes: 44 additions & 6 deletions src/shell/shell-mobile-nav.jsx → src/shell/shell-mobile-nav.tsx
Original file line number Diff line number Diff line change
@@ -4,26 +4,51 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/

import React from 'react';
import React, { ComponentType, FC, FunctionComponent } from 'react';
import { reduce, find } from 'lodash';
import { Accordion, Collapse, Container, Padding } from '@zextras/carbonio-design-system';
import { useHistory } from 'react-router-dom';
import { AppRoute, SecondaryBarComponentProps } from '../../types';
import { useAppStore } from '../store/app';
import AppContextProvider from '../boot/app/app-context-provider';
import { getCurrentRoute } from '../history/hooks';

const SidebarComponent = ({ item }) =>
type SidebarComponentProps = {
item: ShellMobileNavComponent;
};

const SidebarComponent: FC<SidebarComponentProps> = ({ item }) =>
item.secondary ? (
<AppContextProvider pkg={item.id}>
<item.secondary />
<item.secondary expanded={!!item.expanded} />
</AppContextProvider>
) : null;

export default function ShellMobileNav({ mobileNavIsOpen, menuTree }) {
type ShellMobileNavComponentProps = {
mobileNavIsOpen: boolean;
menuTree: AppRoute;
};

type ShellMobileNavComponent = {
id: string;
label: string;
icon: string;
secondary?: ComponentType<SecondaryBarComponentProps>;
onClick?: () => void;
CustomComponent?: FunctionComponent<SidebarComponentProps> | null;
items?: Array<ShellMobileNavComponent> | [];
expanded?: boolean;
};

const ShellMobileNavComponent: FC<ShellMobileNavComponentProps> = ({
mobileNavIsOpen,
menuTree
}) => {
const history = useHistory();
const views = useAppStore((s) =>
reduce(
s.routes,
(acc, val) => {
(acc: Array<ShellMobileNavComponent>, val) => {
const primary = find(s.views.primaryBar, (item) => item.id === val.id);
const secondary = find(s.views.secondaryBar, (item) => item.id === val.id);
if (primary && primary.visible) {
@@ -85,4 +110,17 @@ export default function ShellMobileNav({ mobileNavIsOpen, menuTree }) {
</Collapse>
</Container>
);
}
};

const ShellMobileNavMemo = React.memo(ShellMobileNavComponent);

type ShellMobileNavProps = {
mobileNavIsOpen: boolean;
};

const ShellMobileNav: FC<ShellMobileNavProps> = ({ mobileNavIsOpen }) => {
const menuTree = getCurrentRoute() as AppRoute;
return <ShellMobileNavMemo menuTree={menuTree} mobileNavIsOpen={mobileNavIsOpen} />;
};

export default ShellMobileNav;
10 changes: 4 additions & 6 deletions src/shell/shell-navigation-bar.tsx
Original file line number Diff line number Diff line change
@@ -11,14 +11,12 @@ import ShellSecondaryBar from './shell-secondary-bar';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import ShellMobileNav from './shell-mobile-nav';
import { AppRoute } from '../../types';

type ShellNavigationBarProps = {
mobileNavIsOpen: boolean;
activeRoute: AppRoute;
};

const ShellNavigationBar: FC<ShellNavigationBarProps> = ({ mobileNavIsOpen, activeRoute }) => (
const ShellNavigationBar: FC<ShellNavigationBarProps> = ({ mobileNavIsOpen }) => (
<Container
orientation="horizontal"
background="gray5"
@@ -28,11 +26,11 @@ const ShellNavigationBar: FC<ShellNavigationBarProps> = ({ mobileNavIsOpen, acti
crossAlignment="flex-start"
>
<Responsive mode="desktop">
<ShellPrimaryBar activeRoute={activeRoute} />
<ShellSecondaryBar activeRoute={activeRoute} />
<ShellPrimaryBar />
<ShellSecondaryBar />
</Responsive>
<Responsive mode="mobile">
<ShellMobileNav mobileNavIsOpen={mobileNavIsOpen} menuTree={activeRoute} />
<ShellMobileNav mobileNavIsOpen={mobileNavIsOpen} />
</Responsive>
</Container>
);
10 changes: 9 additions & 1 deletion src/shell/shell-primary-bar.tsx
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ import BadgeWrap from './badge-wrap';
import AppContextProvider from '../boot/app/app-context-provider';
import { checkRoute } from '../utility-bar/utils';
import { IS_STANDALONE } from '../constants';
import { useCurrentRoute } from '../history/hooks';

const ContainerWithDivider = styled(Container)`
border-right: 1px solid ${({ theme }): string => theme.palette.gray3.regular};
@@ -86,7 +87,7 @@ const PrimaryBarAccessoryElement: FC<PrimaryBarAccessoryItemProps> = ({ view })
</Tooltip>
);

const ShellPrimaryBar: FC<{ activeRoute: AppRoute }> = ({ activeRoute }) => {
const ShellPrimaryBarComponent: FC<{ activeRoute: AppRoute }> = ({ activeRoute }) => {
const primaryBarViews = useAppStore((s) => s.views.primaryBar);
const [routes, setRoutes] = useState<Record<string, string>>({});
const history = useHistory();
@@ -163,4 +164,11 @@ const ShellPrimaryBar: FC<{ activeRoute: AppRoute }> = ({ activeRoute }) => {
);
};

const MemoShellPrimaryBarComponent = React.memo(ShellPrimaryBarComponent);

const ShellPrimaryBar: FC = () => {
const activeRoute = useCurrentRoute() as AppRoute;
return <MemoShellPrimaryBarComponent activeRoute={activeRoute} />;
};

export default ShellPrimaryBar;
14 changes: 9 additions & 5 deletions src/shell/shell-secondary-bar.tsx
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ import { useAppStore } from '../store/app';
import { AppRoute } from '../../types';
import { useUtilityBarStore } from '../utility-bar';
import { checkRoute } from '../utility-bar/utils';
import { useCurrentRoute } from '../history/hooks';

const SidebarContainer = styled(Container)`
min-width: 48px;
@@ -24,11 +25,7 @@ const SidebarContainer = styled(Container)`
overflow-x: hidden;
`;

type SecondaryBarProps = {
activeRoute: AppRoute;
};

const ShellSecondaryBar: FC<SecondaryBarProps> = ({ activeRoute }) => {
const ShellSecondaryBarComponent: FC<{ activeRoute: AppRoute }> = ({ activeRoute }) => {
const isOpen = useUtilityBarStore((s) => s.secondaryBarState);
const setIsOpen = useUtilityBarStore((s) => s.setSecondaryBarState);
const onCollapserClick = useCallback(() => setIsOpen(!isOpen), [isOpen, setIsOpen]);
@@ -84,4 +81,11 @@ const ShellSecondaryBar: FC<SecondaryBarProps> = ({ activeRoute }) => {
);
};

const MemoShellSecondaryBar = React.memo(ShellSecondaryBarComponent);

const ShellSecondaryBar: FC = () => {
const activeRoute = useCurrentRoute() as AppRoute;
return <MemoShellSecondaryBar activeRoute={activeRoute} />;
};

export default ShellSecondaryBar;
51 changes: 31 additions & 20 deletions src/shell/shell-view.tsx
Original file line number Diff line number Diff line change
@@ -55,34 +55,35 @@ function DarkReaderListener(): null {
return null;
}

const useLoginRedirection = (activeRoute?: AppRoute): void => {
const useLoginRedirection = (allowUnauthenticated?: string): void => {
const auth = useAccountStore((s) => s.authenticated);
useEffect(() => {
if (IS_STANDALONE && !auth && activeRoute && !activeRoute.standalone?.allowUnauthenticated) {
if (IS_STANDALONE && !auth && !allowUnauthenticated) {
goToLogin();
}
}, [activeRoute, auth]);
}, [allowUnauthenticated, auth]);
};

export const Shell: FC = () => {
const ShellComponent: FC<{ allowUnauthenticated?: string; hideShellHeader?: string }> = ({
allowUnauthenticated,
hideShellHeader
}) => {
const [mobileNavOpen, setMobileNavOpen] = useState(false);
const activeRoute = useCurrentRoute() as AppRoute;
useLoginRedirection(activeRoute);
useLoginRedirection(allowUnauthenticated);
return (
<Background>
<DarkReaderListener />
{/* <MainAppRerouter /> */}
{!(IS_STANDALONE && activeRoute?.standalone?.hideShellHeader) && (
{!(IS_STANDALONE && hideShellHeader) && (
<ShellHeader
activeRoute={activeRoute}
mobileNavIsOpen={mobileNavOpen}
onMobileMenuClick={(): void => setMobileNavOpen(!mobileNavOpen)}
>
<ShellUtilityBar />
</ShellHeader>
)}
<Row crossAlignment="unset" style={{ position: 'relative', flexGrow: '1' }}>
<ShellNavigationBar activeRoute={activeRoute} mobileNavIsOpen={mobileNavOpen} />
<ShellNavigationBar mobileNavIsOpen={mobileNavOpen} />
<AppViewContainer />
<ShellUtilityPanel />
</Row>
@@ -93,16 +94,26 @@ export const Shell: FC = () => {
);
};

const ShellView: FC = () => (
<ShellContextProvider>
<ModalManager>
<SnackbarManager>
<PreviewManager>
<Shell />
</PreviewManager>
</SnackbarManager>
</ModalManager>
</ShellContextProvider>
);
const MemoShell = React.memo(ShellComponent);

const ShellView: FC = () => {
const activeRoute = useCurrentRoute() as AppRoute;
const allowUnauthenticated = activeRoute?.standalone?.allowUnauthenticated as string | undefined;
const hideShellHeader = activeRoute?.standalone?.hideShellHeader as string | undefined;
return (
<ShellContextProvider>
<ModalManager>
<SnackbarManager>
<PreviewManager>
<MemoShell
allowUnauthenticated={allowUnauthenticated}
hideShellHeader={hideShellHeader}
/>
</PreviewManager>
</SnackbarManager>
</ModalManager>
</ShellContextProvider>
);
};

export default ShellView;
7 changes: 0 additions & 7 deletions types/search/index.d.ts
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/

import { AppRoute } from '../apps';
import { QueryChip } from './items';

export * from './items';
@@ -17,12 +16,6 @@ export type SearchState = {
updateModule: (module: string) => void;
};

export type SearchBarProps = {
activeRoute: AppRoute;
primaryAction?: unknown;
secondaryActions?: unknown;
};

// export type SelectLabelFactoryProps = {
// selected: [{ label: string; value: string }];
// open: boolean;

0 comments on commit 5531f4c

Please sign in to comment.