UNPKG

tlvince-react-timeago

Version:

A simple Time-Ago component for ReactJs

205 lines (181 loc) 5.01 kB
/* @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> ) } }