1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-10-04 11:17:02 +02:00
unleash.unleash/frontend/src/component/project/ProjectAccess/ProjectGroupView/ProjectGroupView.tsx
Nuno Góis 233e06ec6a
fix: disable autoResetHiddenColumns when useConditionallyHiddenColumns (#2851)
https://linear.app/unleash/issue/2-563/fix-issue-with-useconditionallyhiddencolumns-and-react-table

It seems like we should add `autoResetHiddenColumns: false` to
`useTable` whenever we use `useConditionallyHiddenColumns`.

Basically the thought is that, if we're controlling column visibility in
our own way, we should not want other things to change that state
unpredictably, otherwise this may make React go _brrrrrr_. And it can be
very hard to pinpoint what exactly may be causing React to go _brrrrrr_.


![image](https://user-images.githubusercontent.com/14320932/211332339-95918c5c-e3ea-40e9-b8b4-756a798a4702.png)

First detected this issue apparently randomly while developing the new
SA table. Around 10-20 page refreshes would eventually trigger it. Was
not easy to find, but hopefully this fixes it permanently. At least I
haven't been able to reproduce it since. Maybe someone has a better idea
of where the issue could be or if this is a pretty good guess. Doesn't
seem like this change hurts us anyways.

I love React, `useEffect` and these very to-the-point error messages.
Very fun and productive.

Reference: https://react-table-v7.tanstack.com/docs/api/useTable
2023-01-10 08:15:12 +00:00

277 lines
9.6 KiB
TypeScript

import { Delete, Edit } from '@mui/icons-material';
import { styled, useMediaQuery, useTheme } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { PageContent } from 'component/common/PageContent/PageContent';
import { PageHeader } from 'component/common/PageHeader/PageHeader';
import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import { Search } from 'component/common/Search/Search';
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
import { TablePlaceholder, VirtualizedTable } from 'component/common/Table';
import { DateCell } from 'component/common/Table/cells/DateCell/DateCell';
import { HighlightCell } from 'component/common/Table/cells/HighlightCell/HighlightCell';
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
import { TimeAgoCell } from 'component/common/Table/cells/TimeAgoCell/TimeAgoCell';
import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext';
import { UserAvatar } from 'component/common/UserAvatar/UserAvatar';
import { UPDATE_PROJECT } from 'component/providers/AccessProvider/permissions';
import { useSearch } from 'hooks/useSearch';
import { IGroup, IGroupUser } from 'interfaces/group';
import { VFC, useState } from 'react';
import { SortingRule, useFlexLayout, useSortBy, useTable } from 'react-table';
import { sortTypes } from 'utils/sortTypes';
import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
const StyledPageContent = styled(PageContent)(({ theme }) => ({
height: '100vh',
overflow: 'auto',
padding: theme.spacing(7.5, 6),
[theme.breakpoints.down('md')]: {
padding: theme.spacing(4, 2),
},
'& .header': {
padding: theme.spacing(0, 0, 2, 0),
},
'& .body': {
padding: theme.spacing(3, 0, 0, 0),
},
borderRadius: `${theme.spacing(1.5, 0, 0, 1.5)} !important`,
}));
const StyledTitle = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
'& > span': {
color: theme.palette.text.secondary,
fontSize: theme.fontSizes.bodySize,
},
}));
const defaultSort: SortingRule<string> = { id: 'joinedAt' };
const columns = [
{
Header: 'Avatar',
accessor: 'imageUrl',
Cell: ({ row: { original: user } }: any) => (
<TextCell>
<UserAvatar user={user} />
</TextCell>
),
maxWidth: 85,
disableSortBy: true,
},
{
id: 'name',
Header: 'Name',
accessor: (row: IGroupUser) => row.name || '',
Cell: HighlightCell,
minWidth: 100,
searchable: true,
},
{
id: 'username',
Header: 'Username',
accessor: (row: IGroupUser) => row.username || row.email,
Cell: HighlightCell,
minWidth: 100,
searchable: true,
},
{
id: 'joined',
Header: 'Joined',
accessor: 'joinedAt',
Cell: DateCell,
sortType: 'date',
maxWidth: 150,
},
{
id: 'lastLogin',
Header: 'Last login',
accessor: (row: IGroupUser) => row.seenAt || '',
Cell: ({ row: { original: user } }: any) => (
<TimeAgoCell
value={user.seenAt}
emptyText="Never"
title={date => `Last login: ${date}`}
/>
),
sortType: 'date',
maxWidth: 150,
},
];
const hiddenColumnsSmall = ['imageUrl', 'name', 'joined', 'lastLogin'];
interface IProjectGroupViewProps {
open: boolean;
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
group: IGroup;
projectId: string;
subtitle: string;
onEdit: () => void;
onRemove: () => void;
}
export const ProjectGroupView: VFC<IProjectGroupViewProps> = ({
open,
setOpen,
group,
projectId,
subtitle,
onEdit,
onRemove,
}) => {
const theme = useTheme();
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
const [initialState] = useState(() => ({
sortBy: [
{
id: defaultSort.id,
desc: defaultSort.desc,
},
],
}));
const [searchValue, setSearchValue] = useState('');
const { data, getSearchText, getSearchContext } = useSearch(
columns,
searchValue,
group?.users ?? []
);
const { headerGroups, rows, prepareRow, setHiddenColumns } = useTable(
{
columns: columns as any[],
data,
initialState,
sortTypes,
autoResetHiddenColumns: false,
autoResetSortBy: false,
disableSortRemove: true,
disableMultiSort: true,
},
useSortBy,
useFlexLayout
);
useConditionallyHiddenColumns(
[
{
condition: isSmallScreen,
columns: hiddenColumnsSmall,
},
],
setHiddenColumns,
columns
);
return (
<SidebarModal
open={open}
onClose={() => {
setOpen(false);
}}
label={group?.name || 'Group'}
>
<StyledPageContent
header={
<PageHeader
secondary
titleElement={
<StyledTitle>
{group?.name} (
{rows.length < data.length
? `${rows.length} of ${data.length}`
: data.length}
)<span>{subtitle}</span>
</StyledTitle>
}
actions={
<>
<ConditionallyRender
condition={!isSmallScreen}
show={
<>
<Search
initialValue={searchValue}
onChange={setSearchValue}
hasFilters
getSearchContext={
getSearchContext
}
/>
<PageHeader.Divider />
</>
}
/>
<PermissionIconButton
permission={UPDATE_PROJECT}
projectId={projectId}
tooltipProps={{
title: 'Edit group access',
}}
onClick={onEdit}
>
<Edit />
</PermissionIconButton>
<PermissionIconButton
permission={UPDATE_PROJECT}
projectId={projectId}
tooltipProps={{
title: 'Remove group access',
}}
onClick={onRemove}
>
<Delete />
</PermissionIconButton>
</>
}
>
<ConditionallyRender
condition={isSmallScreen}
show={
<Search
initialValue={searchValue}
onChange={setSearchValue}
hasFilters
getSearchContext={getSearchContext}
/>
}
/>
</PageHeader>
}
>
<SearchHighlightProvider value={getSearchText(searchValue)}>
<VirtualizedTable
rows={rows}
headerGroups={headerGroups}
prepareRow={prepareRow}
/>
</SearchHighlightProvider>
<ConditionallyRender
condition={rows.length === 0}
show={
<ConditionallyRender
condition={searchValue?.length > 0}
show={
<TablePlaceholder>
No users found matching &ldquo;
{searchValue}
&rdquo; in this group.
</TablePlaceholder>
}
elseShow={
<TablePlaceholder>
This group is empty. Get started by adding a
user to the group.
</TablePlaceholder>
}
/>
}
/>
</StyledPageContent>
</SidebarModal>
);
};