Grouped Messages
This example showcases a chat that groups consecutive messages from the same user. The ItemContent
component receives the nextData
and prevData
props, which allow you to determine the position of the message in the conversation. Based on the position, the message is styled differently to indicate the start, middle, or end of a message group.
You can use similar approach to render the users' avatars only once per group, or to display the message timestamp at the top of the group.
Live Editor
interface Message { key: string text: string user: 'me' | 'other' } let idCounter = 0 function randomMessage(user: Message['user']): Message { return { user, key: `${idCounter++}`, text: randTextRange({ min: user === 'me' ? 20 : 100, max: 200 }) } } function App() { const mounted = React.useRef(false) const virtuoso = React.useRef<VirtuosoMessageListMethods<Message>>(null) React.useEffect(() => { if (mounted.current) { return } mounted.current = true setTimeout(() => { virtuoso.current?.data.append( Array.from({ length: 20 }, (_, index) => { const author = ['me', 'other'][index % 4 ? 0 : 1] // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any return randomMessage(author as any) }) ) }) }, []) return ( <div class="tall-example" style={{fontSize: '70%'}}> <VirtuosoMessageListLicense licenseKey=""> <VirtuosoMessageList<Message, null> ref={virtuoso} style={{ height: 800 }} computeItemKey={({ data }) => data.key} initialLocation={{ index: 'LAST', align: 'end' }} ItemContent={({ data, nextData, prevData }) => { let groupType = 'none' if (nextData && nextData.user === data.user) { if (prevData && prevData.user === data.user) { groupType = 'middle' } else { groupType = 'top' } } else if (prevData && prevData.user === data.user) { groupType = 'bottom' } const borderRadiusStyle = { none: '1rem', top: '1rem 1rem 0.3rem 0.3rem', middle: '0.3rem', bottom: '0.3rem 0.3rem 1rem 1rem', }[groupType] const paddingBottomStyle = { none: '2rem', top: '0.2rem', middle: '0.2rem', bottom: '1rem', }[groupType] return ( <div style={{ paddingBottom: paddingBottomStyle, display: 'flex' }}> <div style={{ maxWidth: '50%', marginLeft: data.user === 'me' ? 'auto' : undefined, backgroundColor: data.user === 'me' ? '#0253B3' : '#E6B253', color: data.user === 'me' ? 'white' : 'black', borderRadius: borderRadiusStyle, padding: '1rem', }} > {data.text} </div> </div> ) }} /> </VirtuosoMessageListLicense> <button onClick={() => { virtuoso.current?.data.append([randomMessage('me')], ({ scrollInProgress, atBottom }) => { if (atBottom || scrollInProgress) { return 'smooth' } else { return 'auto' } }) }} > Send message </button> <button onClick={() => { virtuoso.current?.data.append([randomMessage('other')], ({ scrollInProgress, atBottom }) => { if (atBottom || scrollInProgress) { return 'smooth' } else { return false } }) }} > Receive message </button> </div> ) } render(<App />)
Result
Loading...