Skip to main content

Date Separators

Date separators are an useful addition to long conversations, making it easier for the user to follow the conversation flow. The message list component allows you to implement separators in the ItemContent message by passing prevData and nextData props.

In addition to the separators, you might want to display a sticky date header, which is always visible when the messages are scrolled. To achieve this, you can use the useCurrentlyRenderedData hook to get the first message and display the date of the first message.

The example below demonstrates how to implement date separators in a message list.

Live Editor
interface Message {
  key: string
  text: string
  user: 'me' | 'other'
  date: Date
}

let idCounter = 0

// fake consecutive dates for the messages
const startDate = new Date()
startDate.setDate(startDate.getDate() - 50)

function randomMessage(user: Message['user']): Message {
  return { 
    user, 
    key: `${idCounter++}`, 
    date: new Date(startDate.getTime() + idCounter * 1000 * 60 * 60 * 4), 
    text: randTextRange({ min: user === 'me' ? 20 : 100, max: 200 }) 
  }
}

const StickyHeader: VirtuosoMessageListProps<Message, null>['StickyHeader'] = ({ data, prevData }) => {
  const firstItem = useCurrentlyRenderedData<{ date: Date }>()[0] as { date: Date } | undefined
  return (
    <div style={{ width: '100%', position: 'absolute', top: 0 }}>
      <div style={{ textAlign: 'center', fontWeight: 300 }}><span style={{backgroundColor: '#F0F0F3', padding: '0.1rem 2rem', borderRadius: '0.5rem' }}>{firstItem?.date.toDateString()}</span></div>
    </div>
  )
}

const ItemContent: VirtuosoMessageListProps<Message, null>['ItemContent'] = ({ data, prevData }) => {
  const dateSeparator =
    !prevData || prevData.date.getDate() !== data.date.getDate() ? (
      <div>
        <div style={{ textAlign: 'center', fontWeight: 300 }}><span style={{backgroundColor: '#F0F0F3', padding: '0.1rem 2rem', borderRadius: '0.5rem' }}>{data.date.toDateString()}</span></div>
      </div>
    ) : null

  return (
    <>
    {dateSeparator}
    <div
      style={{
        paddingBottom: '2rem',
        display: 'flex',
        flexDirection: data.user === 'me' ? 'row-reverse' : 'row',
      }}
    >
      <div
        style={{
          maxWidth: '80%',
          display: 'flex',
          flexDirection: data.user === 'me' ? 'row-reverse' : 'row',
          alignItems: 'center',
        }}
      >
        <div
          style={{
            backgroundColor: data.user === 'me' ? '#0253B3' : '#F0F0F3',
            color: data.user === 'me'  ? 'white' : 'black',
            borderRadius: '1rem',
            padding: '1rem',
          }}
        >
          {data.text}
        </div>
      </div>
    </div>
  </>
  )
}

function App() {
  const messages = React.useMemo(() => Array.from({ length: 100 }, () => randomMessage(rand(['me', 'other']))), [])

  return (
    <div class="tall-example" style={{fontSize: '70%'}}>
      <VirtuosoMessageListLicense licenseKey="">
        <VirtuosoMessageList<Message, null>
          initialData={messages}
          style={{ height: 800 }}
          computeItemKey={({ data }) => data.key}
          initialLocation={{ index: 'LAST', align: 'end' }}
          ItemContent={ItemContent}
          StickyHeader={StickyHeader}
        />
      </VirtualMessageListLicense>
    </div>
  )
}

render(<App />)
Result
Loading...