227 lines
6.8 KiB
Markdown
227 lines
6.8 KiB
Markdown
# ValidationTable Scroll Position Issue
|
|
|
|
## Problem Description
|
|
|
|
The `ValidationTable` component in the inventory application suffers from a persistent scroll position issue. When the table content updates or re-renders, the scroll position resets to the top left corner. This creates a poor user experience, especially when users are working with large datasets and need to maintain their position while making edits or filtering data.
|
|
|
|
Specific behaviors:
|
|
- Scroll position resets to the top left corner during re-renders
|
|
- User loses their place in the table when data is updated
|
|
- The table does not preserve vertical or horizontal scroll position
|
|
|
|
## Relevant Files
|
|
|
|
- **`inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationTable.tsx`**
|
|
- Main component that renders the validation table
|
|
- Handles scroll position management
|
|
|
|
- **`inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationContainer.tsx`**
|
|
- Parent component that wraps ValidationTable
|
|
- Creates an EnhancedValidationTable wrapper component
|
|
- Manages data and state for the validation table
|
|
|
|
- **`inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/hooks/useValidationState.tsx`**
|
|
- Provides state management and data manipulation functions
|
|
- Contains scroll-related code in the `updateRow` function
|
|
|
|
- **`inventory/src/lib/react-spreadsheet-import/src/steps/ValidationStepNew/components/ValidationCell.tsx`**
|
|
- Renders individual cells in the table
|
|
- May influence re-renders that affect scroll position
|
|
|
|
## Failed Attempts
|
|
|
|
We've tried multiple approaches to fix the scroll position issue, none of which have been successful:
|
|
|
|
### 1. Using Refs for Scroll Position
|
|
|
|
```typescript
|
|
const scrollPosition = useRef({
|
|
left: 0,
|
|
top: 0
|
|
});
|
|
|
|
// Capture position on scroll
|
|
const handleScroll = useCallback(() => {
|
|
if (tableContainerRef.current) {
|
|
scrollPosition.current = {
|
|
left: tableContainerRef.current.scrollLeft,
|
|
top: tableContainerRef.current.scrollTop
|
|
};
|
|
}
|
|
}, []);
|
|
|
|
// Restore in useLayoutEffect
|
|
useLayoutEffect(() => {
|
|
const container = tableContainerRef.current;
|
|
if (container) {
|
|
const { left, top } = scrollPosition.current;
|
|
if (left || top) {
|
|
container.scrollLeft = left;
|
|
container.scrollTop = top;
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
Result: Scroll position was still lost during updates.
|
|
|
|
### 2. Multiple Restoration Attempts with Timeouts
|
|
|
|
```typescript
|
|
// Multiple timeouts at different intervals
|
|
setTimeout(() => {
|
|
if (tableContainerRef.current) {
|
|
tableContainerRef.current.scrollTop = savedPosition.top;
|
|
tableContainerRef.current.scrollLeft = savedPosition.left;
|
|
}
|
|
}, 0);
|
|
|
|
setTimeout(() => {
|
|
if (tableContainerRef.current) {
|
|
tableContainerRef.current.scrollTop = savedPosition.top;
|
|
tableContainerRef.current.scrollLeft = savedPosition.left;
|
|
}
|
|
}, 50);
|
|
|
|
// Additional timeouts at 100ms, 300ms
|
|
```
|
|
|
|
Result: Still not reliable, scroll position would reset between timeouts or after all timeouts completed.
|
|
|
|
### 3. Using MutationObserver and ResizeObserver
|
|
|
|
```typescript
|
|
// Create a mutation observer to detect DOM changes
|
|
const mutationObserver = new MutationObserver(() => {
|
|
if (shouldPreserveScroll) {
|
|
restoreScrollPosition();
|
|
}
|
|
});
|
|
|
|
// Start observing the table for DOM changes
|
|
mutationObserver.observe(scrollableContainer, {
|
|
childList: true,
|
|
subtree: true,
|
|
attributes: true,
|
|
attributeFilter: ['style', 'class']
|
|
});
|
|
|
|
// Create a resize observer
|
|
const resizeObserver = new ResizeObserver(() => {
|
|
if (shouldPreserveScroll) {
|
|
restoreScrollPosition();
|
|
}
|
|
});
|
|
|
|
// Observe the table container
|
|
resizeObserver.observe(scrollableContainer);
|
|
```
|
|
|
|
Result: Did not reliably maintain scroll position, and sometimes caused other rendering issues.
|
|
|
|
### 4. Recursive Restoration Approach
|
|
|
|
```typescript
|
|
let attempts = 0;
|
|
const maxAttempts = 5;
|
|
|
|
const restore = () => {
|
|
if (tableContainerRef.current) {
|
|
tableContainerRef.current.scrollTop = y;
|
|
tableContainerRef.current.scrollLeft = x;
|
|
|
|
attempts++;
|
|
if (attempts < maxAttempts) {
|
|
setTimeout(restore, 50 * attempts);
|
|
}
|
|
}
|
|
};
|
|
|
|
restore();
|
|
```
|
|
|
|
Result: No improvement, scroll position still reset.
|
|
|
|
### 5. Using React State for Scroll Position
|
|
|
|
```typescript
|
|
const [scrollPos, setScrollPos] = useState<{top: number; left: number}>({top: 0, left: 0});
|
|
|
|
// Track the scroll event
|
|
useEffect(() => {
|
|
const handleScroll = () => {
|
|
if (scrollContainerRef.current) {
|
|
setScrollPos({
|
|
top: scrollContainerRef.current.scrollTop,
|
|
left: scrollContainerRef.current.scrollLeft
|
|
});
|
|
}
|
|
};
|
|
|
|
// Add scroll listener...
|
|
}, []);
|
|
|
|
// Restore scroll position
|
|
useLayoutEffect(() => {
|
|
const container = scrollContainerRef.current;
|
|
const { top, left } = scrollPos;
|
|
|
|
if (top > 0 || left > 0) {
|
|
requestAnimationFrame(() => {
|
|
if (container) {
|
|
container.scrollTop = top;
|
|
container.scrollLeft = left;
|
|
}
|
|
});
|
|
}
|
|
}, [scrollPos, data]);
|
|
```
|
|
|
|
Result: Caused the screen to shake violently when scrolling and did not preserve position.
|
|
|
|
### 6. Using Key Attribute for Stability
|
|
|
|
```typescript
|
|
return (
|
|
<div
|
|
key="validation-table-container"
|
|
ref={scrollContainerRef}
|
|
className="overflow-auto max-h-[calc(100vh-300px)]"
|
|
>
|
|
{/* Table content */}
|
|
</div>
|
|
);
|
|
```
|
|
|
|
Result: Did not resolve the issue and may have contributed to rendering instability.
|
|
|
|
### 7. Removing Scroll Management from Other Components
|
|
|
|
We removed scroll position management code from:
|
|
- `useValidationState.tsx` (in the updateRow function)
|
|
- `ValidationContainer.tsx` (in the enhancedUpdateRow function)
|
|
|
|
Result: This did not fix the issue either.
|
|
|
|
## Current Understanding
|
|
|
|
The scroll position issue appears to be complex and likely stems from multiple factors:
|
|
|
|
1. React's virtual DOM reconciliation may be replacing the scroll container element during updates
|
|
2. The table uses complex memo patterns with custom equality checks that may not be working as expected
|
|
3. The data structure may be changing in ways that cause complete re-renders
|
|
4. The component hierarchy (with EnhancedValidationTable wrapper) may be affecting DOM stability
|
|
|
|
## Next Steps to Consider
|
|
|
|
Potential approaches that haven't been tried yet:
|
|
|
|
1. Implement a completely separate scroll container that exists outside of React's rendering cycle
|
|
2. Use a third-party virtualized table library that handles scroll position natively
|
|
3. Restructure the component hierarchy to minimize re-renders
|
|
4. Use the React DevTools profiler to identify which components are causing re-renders
|
|
5. Consider simplifying the data structure to reduce the complexity of renders
|
|
|
|
## Conclusion
|
|
|
|
This issue has proven particularly challenging to resolve. The current ValidationTable implementation struggles with scroll position preservation despite multiple different approaches. A more fundamental restructuring of the component or its rendering approach may be necessary. |