-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: record board component state refactor #8779
feat: record board component state refactor #8779
Conversation
940d4b4
to
14ebef5
Compare
recordBoardId, | ||
); | ||
|
||
const recordBoardSelectedRecordIdsSelector = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should also extract selection at recordIndex level too (but that's another story!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I think too, I didn't make it here because the PR was big enough 😅
import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; | ||
import { isDefined } from '~/utils/isDefined'; | ||
|
||
export const useSetRecordIdsForColumn = (recordBoardId?: string) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks similar to useSetRecordBoardRecordIds?
naming is not consistent: useSetRecordIdsForColumn + do we need to do it twice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's similar but not exactly doing the same, I didn't take time to figure out why we need both of them, but they're used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, could you add a TODO?
); | ||
|
||
const recordIds = useRecoilValue( | ||
recordIdsByColumnIdFamilyState(recordBoardColumnId), | ||
const recordRowIdsByGroup = useRecoilComponentFamilyValueV2( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be recordCardIdsByGroup here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just for me to double check: what is rowIds vs recordIds? is it actually recordIds?
to me rowIds are only useful to navigate between records using the keyboard, I'm a bit surprise we have this on Kanban
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually it's record ids (uuids), I'm a bit confused too with the naming as before states was names with rowIds, but I think we should use recordIds everywhere ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay, I think we should rename them all to recordIds, it would be way clearer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR Summary
This PR implements a significant refactoring of the record board component state management, moving from hook-based to component-scoped Recoil state management.
- Replaced
useRecordBoardStates
and related hooks with direct component-scoped Recoil states usinguseRecoilComponentValueV2
anduseRecoilComponentFamilyStateV2
- Migrated from table-specific states to more generic record index states (e.g.,
tableAllRowIdsComponentState
->recordIndexAllRowIdsComponentState
) - Introduced
RecordBoardComponentInstanceContext
for better state isolation and management - Added new utility functions like
recordGroupDefinitionToViewGroup
andsortedInsert
to improve code organization - Improved handling of 'No Value' groups in view group mapping with proper UUID generation and positioning
💡 (4/5) You can add custom instructions or style guidelines for the bot here!
95 file(s) reviewed, 41 comment(s)
Edit PR Review Bot Settings | Greptile
@@ -113,7 +114,7 @@ export const ObjectOptionsDropdownMenuContent = () => { | |||
onClick={() => onContentChange('recordGroups')} | |||
LeftIcon={IconLayoutList} | |||
text="Group by" | |||
contextualText={viewGroupFieldMetadataItem?.label} | |||
contextualText={recordGroupFieldMetadata?.label} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: optional chaining here could result in 'undefined' being displayed - consider adding a default value
useEffect(() => { | ||
if ( | ||
currentContentId === 'hiddenRecordGroups' && | ||
hiddenRecordGroups.length === 0 | ||
hiddenRecordGroupIds.length === 0 | ||
) { | ||
onContentChange('recordGroups'); | ||
} | ||
}, [hiddenRecordGroups, currentContentId, onContentChange]); | ||
}, [hiddenRecordGroupIds, currentContentId, onContentChange]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: this useEffect could potentially be moved to a shared hook since the same pattern appears in multiple dropdown content components
onColumnsChange={() => {}} | ||
onFieldsChange={() => {}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Empty callback functions should be implemented or removed if not needed
<RecordBoardColumnHeaderWrapper columnId={columnId} key={columnId} /> | ||
{visibleRecordGroupIds.map((recordGroupId) => ( | ||
<RecordBoardColumnHeaderWrapper | ||
columnId={recordGroupId} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: columnId prop is being passed a recordGroupId value - consider renaming the prop to match the new data structure
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: This file managed critical state for record board functionality including filters, sorts, field definitions, and selection states. Ensure all this functionality is preserved in the new implementation.
if (!componentInstanceContext) { | ||
throw new Error( | ||
`Instance context for key "${componentState.key}" is not defined`, | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: error message should include more context about how to properly initialize the context
const existingField = currentViewGroups.find( | ||
(currentViewGroup) => | ||
currentViewGroup.fieldValue === viewGroupToSave.fieldValue, | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: variable name 'existingField' is inconsistent with type 'ViewGroup', should be 'existingViewGroup' for clarity
await updateViewGroupRecords([ | ||
{ ...viewGroupToSave, id: existingField.id }, | ||
]); | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: saveViewGroup doesn't handle creation of new view groups, unlike saveViewGroups. This inconsistency could lead to bugs
viewGroup?.position ?? | ||
recordGroupDefinitionsFromViewGroups | ||
.map((option) => option.position) | ||
.reduce((a, b) => Math.max(a, b), 0) + 1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: potential error if recordGroupDefinitionsFromViewGroups is empty - reduce will throw
fieldMetadataId: recordGroup.fieldMetadataId, | ||
position: recordGroup.position, | ||
isVisible: recordGroup.isVisible ?? true, | ||
fieldValue: recordGroup.value ?? '', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: fieldValue should be nullable to match potential null values from the backend
|
||
export const useCurrentRecordGroupDefinition = (recordTableId?: string) => { | ||
export const useCurrentRecordGroupDefinition = () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why "Current here"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's because we're getting the RecordGroup based on the current recordGroupId present in the context, but we can change the naming if you want
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition'; | ||
import { atomFamily } from 'recoil'; | ||
|
||
export const recordGroupDefinitionFamilyState = atomFamily< |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is the recordGroupDefinition used for? I feel the "Definition" pattern is not useful anymore and could be replaced by utils
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RecordGroupDefinition is almost a mapping of view-groups, at first it was added because of the NoValue group that is not stored in the backend side. But as we now wan't it to act as a normal RecordGroup this one is stored in the backend too.
But I think we should keep it now for consistency in the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, let's do a follow up
>({ | ||
key: 'recordGroupFieldMetadataComponentState', | ||
defaultValue: undefined, | ||
componentInstanceContext: ViewComponentInstanceContext, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should not use ViewComponentInstanceContext in RecordGroups
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The context war haha, actually mixing context between different modules can be tricky because some selectors or are mixing some states from different modules.
It's a bit due to the refactor, but also due to the structure of record-group, maybe we should move it inside record-index.
Mixing states in selectors can actually work as the instanceId between context is often the same, but it's not a good option.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, ok, let's do that in a folllow up
string[] | ||
>({ | ||
key: 'hiddenRecordGroupIdsComponentSelector', | ||
componentInstanceContext: ViewComponentInstanceContext, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same everywhere :)
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; | ||
import { isDefined } from '~/utils/isDefined'; | ||
|
||
export const recordGroupDefinitionsComponentSelector = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
@@ -0,0 +1,20 @@ | |||
export const sortedInsert = <T>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a big fan of this function which is doing two things: sorting + insert. Not sure we need a combined logic here.
why not just sorting where needed and inserting when needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I make this util to avoid looping too many times on the recordGroupIds for performance purpose.
By using it it's reduced to 2 loop, and I think that can help with big tables.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, let's give it a local naming then: recordGroupSortIndex so people don't start using it
>({ | ||
key: 'recordIndexAllRowIdsComponentState', | ||
defaultValue: [], | ||
componentInstanceContext: ViewComponentInstanceContext, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
@@ -20,6 +20,56 @@ export const useSaveCurrentViewGroups = (viewBarComponentId?: string) => { | |||
viewBarComponentId, | |||
); | |||
|
|||
const saveViewGroup = useRecoilCallback( | |||
({ snapshot }) => | |||
async (viewGroupToSave: ViewGroup) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
views hooks should expose RecordGroups in its API and internally convert it to view Group, this way everybody is taking dependency on RecordGroup (types) and ViewGroups is only in ViewBar
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll take a look on that, maybe in another PR ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, please discuss with Lucas too on this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bravo!
Fix #8758
This PR is migrating the recoil component state from v1 to v2 for board.
It also now share some states and logics between board and table, further can be done later.
Lastly this PR fix an issue since the PR #8613 that was treating no-value as a normal record-group.