tlvince-react-timeago
Version:
A simple Time-Ago component for ReactJs
205 lines (181 loc) • 5.01 kB
Flow
/* @flow */
import React, {Component} from 'react'
import defaultFormatter from './defaultFormatter'
import dateParser from './dateParser'
export type Unit =
| 'second'
| 'minute'
| 'hour'
| 'day'
| 'week'
| 'month'
| 'year'
export type Suffix =
| 'ago'
| 'from now'
export type Formatter = (
value: number,
unit: Unit,
suffix: Suffix,
epochSeconds: number,
nextFormatter?: Formatter
) => string | React$Element<any>
export type Props = {
/** If the component should update itself over time */
+live: boolean,
/** minimum amount of time in seceonds between re-renders */
+minPeriod: number,
/** Maximum time between re-renders in seconds.
* The component should update at least once every `x` seconds
*/
+maxPeriod: number,
/** The container to render the string into.
* You could use a string like `span` or a custom component
*/
+component: string | ReactClass<any>,
/**
* A title used for setting the title attribute if a <time> Element is used.
*/
+title?: string,
/** A function to decide how to format the date.
* If you use this, react-timeago is basically acting like a glorified
* setInterval for you.
*/
+formatter: Formatter,
/** The Date to display. An actual Date object or something that can be fed
* to new Date
*/
+date: string | number | Date,
/** A function that returns what Date.now would return. Primarily for server
* rendering.
*/
+now: () => number
}
type DefaultProps = {
+live: boolean,
+minPeriod: number,
+maxPeriod: number,
+component: string | ReactClass<Object> | Function,
+formatter: Formatter,
+now: () => number
}
const MINUTE = 60
const HOUR = MINUTE * 60
const DAY = HOUR * 24
const WEEK = DAY * 7
const MONTH = DAY * 30
const YEAR = DAY * 365
export default class TimeAgo extends Component<DefaultProps, Props, void> {
static displayName = 'TimeAgo'
static defaultProps = {
live: true,
component: 'time',
minPeriod: 0,
maxPeriod: Infinity,
formatter: defaultFormatter,
now: () => Date.now()
}
timeoutId: ?number
isStillMounted: boolean = false
tick = (refresh: ?boolean): void => {
if (!this.isStillMounted || !this.props.live) {
return
}
const then = dateParser(this.props.date).valueOf()
if (!then) {
console.warn('[react-timeago] Invalid Date provided')
return
}
const now = this.props.now()
const seconds = Math.round(Math.abs(now - then) / 1000)
const unboundPeriod
= seconds < MINUTE
? 1000
: seconds < HOUR
? 1000 * MINUTE
: seconds < DAY
? 1000 * HOUR
: 0
const period = Math.min(
Math.max(unboundPeriod, this.props.minPeriod * 1000),
this.props.maxPeriod * 1000
)
if (period) {
this.timeoutId = setTimeout(this.tick, period)
}
if (!refresh) {
this.forceUpdate()
}
}
componentDidMount () {
this.isStillMounted = true
if (this.props.live) {
this.tick(true)
}
}
componentDidUpdate (lastProps: Props) {
if (this.props.live !== lastProps.live || this.props.date !== lastProps.date) {
if (!this.props.live && this.timeoutId) {
clearTimeout(this.timeoutId)
}
this.tick()
}
}
componentWillUnmount () {
this.isStillMounted = false
if (this.timeoutId) {
clearTimeout(this.timeoutId)
this.timeoutId = undefined
}
}
render (): ?React$Element<*> {
/* eslint-disable no-unused-vars */
const {
date,
formatter,
component: Komponent,
live,
minPeriod,
maxPeriod,
title,
...passDownProps
} = this.props
/* eslint-enable no-unused-vars */
const then = dateParser(date).valueOf()
if (!then) {
return null
}
const now = this.props.now()
const seconds = Math.round(Math.abs(now - then) / 1000)
const suffix = then < now ? 'ago' : 'from now'
const [value, unit]
= seconds < MINUTE
? [Math.round(seconds), 'second']
: seconds < HOUR
? [Math.round(seconds / MINUTE), 'minute']
: seconds < DAY
? [Math.round(seconds / HOUR), 'hour']
: seconds < WEEK
? [Math.round(seconds / DAY), 'day']
: seconds < MONTH
? [Math.round(seconds / WEEK), 'week']
: seconds < YEAR
? [Math.round(seconds / MONTH), 'month']
: [Math.round(seconds / YEAR), 'year']
const passDownTitle = typeof title === 'undefined'
? (typeof date === 'string'
? date
: dateParser(date).toISOString().substr(0, 16).replace('T', ' '))
: title
if (Komponent === 'time') {
passDownProps.dateTime = dateParser(date).toISOString()
delete passDownProps.now
}
const nextFormatter = defaultFormatter.bind(null, value, unit, suffix, then)
return (
<Komponent {...passDownProps} title={passDownTitle}>
{this.props.formatter(value, unit, suffix, then, nextFormatter)}
</Komponent>
)
}
}