Reputation: 1612
We have a log viewer that is fed from SignalR - basically a live view of logs coming in from a set of services and websites. The log viewer bogs down as more and more dom elements are created, so I was hoping I could use a virtualization library to relieve that pressure. I am trying react-virtual right now, but it doesn't appear my use case works. I've based the following POC on the React examples in the docs.
import { Button, ButtonGroup, CardBody, CardHeader, Icon, Input } from 'our-common-lib';
import * as signalR from '@microsoft/signalr';
import { useVirtualizer } from '@tanstack/react-virtual';
import React, { useCallback, useEffect, useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import Constants from '../../constants';
import { IApplicationState } from '../../store';
import { LogRow } from './LogRow';
const CustomCard = styled.div`
width: 96%;
margin: 30px auto 50px auto;
position: relative;
display: flex;
flex-direction: column;
min-width: 0;
word-wrap: break-word;
background-clip: border-box;
border-radius: 4px;
background-color: #ffffff !important;
color: #444444 !important;
`;
const ButtonsContainer = styled.div`
float: right;
`;
const InputContainer = styled.span`
margin-left: 15px;
`;
const StyledCardBody = styled(CardBody)`
height: 700px;
overflow: auto;
`;
export default function ViewLogs() {
const [autoScroll, setAutoScroll] = React.useState(false);
const allRows = useSelector((state: IApplicationState) => state.logStream.messages || []);
const user = useSelector((state: IApplicationState) => state.oidc.user);
const dispatch = useDispatch();
const parentRef = useRef<HTMLDivElement>(null);
const scrollCount = allRows.length - 1;
const rowVirtualizer = useVirtualizer({
count: allRows.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 35,
overscan: 5,
});
const openConnection = useCallback(() => {
const hubConnection = new signalR.HubConnectionBuilder()
.withUrl(Constants.API_SIGNALR_URL, {
accessTokenFactory: () => user?.access_token,
} as signalR.IHttpConnectionOptions)
.build();
hubConnection.serverTimeoutInMilliseconds = Constants.SignalRServerTimeoutInMilliseconds;
hubConnection.keepAliveIntervalInMilliseconds = Constants.SignalRKeepAliveIntervalInMilliseconds;
hubConnection.start();
hubConnection.on('logStreamMessage', (receivedMessage: string) => {
dispatch({ type: 'logStream/receiveMessage', payload: receivedMessage });
});
return hubConnection;
}, [dispatch, user]);
useEffect(() => {
const hubConnection = openConnection();
return () => {
hubConnection.stop();
};
}, [openConnection]);
useEffect(() => {
if (autoScroll) {
// eslint-disable-next-line no-console
console.log(`scrolling to ${scrollCount}`);
rowVirtualizer.scrollToIndex(scrollCount);
}
}, [autoScroll, rowVirtualizer, scrollCount]);
const rows = rowVirtualizer.getVirtualItems();
return (
<CustomCard>
<CardHeader size='md'>
<InputContainer>
<Input size='md' id='correlationid-filter' placeholder='CorrelationId Filter' />
</InputContainer>
<ButtonsContainer>
<ButtonGroup type='column'>
<Button id='goto-top' onClick={() => rowVirtualizer.scrollToIndex(0)} color='primary'>
Start
</Button>
<Button id='goto-bottom' onClick={() => rowVirtualizer.scrollToIndex(scrollCount)} color='primary'>
End
</Button>
{autoScroll ? (
<Button id='pause-scroll' onClick={() => setAutoScroll(false)} color='primary'>
|| Pause scroll
</Button>
) : (
<Button id='auto-scroll' onClick={() => setAutoScroll(true)} color='primary'>
<Icon name='caret-right' />
Auto Scroll
</Button>
)}
</ButtonGroup>
</ButtonsContainer>
</CardHeader>
<StyledCardBody>
<div ref={parentRef}>
<div
style={{
height: `${rowVirtualizer.getTotalSize()}px`,
width: '100%',
position: 'relative',
}}
>
{rows.map((virtualRow) => {
const text = JSON.parse(allRows[virtualRow.index]);
return (
<div
key={virtualRow.index}
className={virtualRow.index % 2 ? 'ListItemOdd' : 'ListItemEven'}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`,
}}
>
<LogRow logMessage={text} rowNumber={virtualRow.index} />
</div>
);
})}
</div>
</div>
</StyledCardBody>
</CustomCard>
);
}
What I'm seeing is that a row is created in the dom for every item coming in from the feed. The scrolling doesn't work at all.
I'm guessing this is because I'm giving the useVirtualizer
hook a new total count on every render (I'm chunking results in my Redux thunk).
Has anyone come up with a solution using react-virtual that works with an "infinite" feed?
Upvotes: 1
Views: 443