Skip to content

Commit

Permalink
fix(react-intl): fix FormattedRelativeTime update after unmount, fix #…
Browse files Browse the repository at this point in the history
  • Loading branch information
longlho committed Mar 19, 2021
1 parent 3153009 commit bdc0586
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 87 deletions.
37 changes: 37 additions & 0 deletions packages/react-intl/examples/Bug2727.tsx
@@ -0,0 +1,37 @@
import React, {useState} from 'react'
import {IntlProvider, FormattedRelativeTime, useIntl} from 'react-intl'

const getRelativeTime = (date: Date) =>
(date.getTime() - new Date().getTime()) / 1000

const PostDate: React.FC<{date: Date}> = ({date}) => {
const intl = useIntl()
return (
<span title={intl.formatDate(date)}>
<FormattedRelativeTime
value={getRelativeTime(date)}
updateIntervalInSeconds={1}
/>
</span>
)
}

export const App: React.FC<{}> = () => {
const [hide, setHide] = useState(false)
return (
<IntlProvider locale={navigator.language}>
<div>
<h1>Hello world</h1>
{!hide && (
<p>
<PostDate date={new Date()} />
</p>
)}

<button onClick={() => setHide(current => !current)}>
Toggle FormattedRelativeTime
</button>
</div>
</IntlProvider>
)
}
1 change: 1 addition & 0 deletions packages/react-intl/examples/index.html
Expand Up @@ -13,6 +13,7 @@
<div id="injected"></div>
<div id="handlechange"></div>
<div id="static-type-safety-and-code-splitting"></div>
<div id="bug2727"></div>
<script src="./index.tsx"></script>
</body>
</html>
60 changes: 31 additions & 29 deletions packages/react-intl/examples/index.tsx
@@ -1,42 +1,44 @@
import 'core-js/stable';
import '@formatjs/intl-pluralrules/polyfill';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import Timezone from './TimeZone';
import Messages from './Messages';
import Hooks from './Hooks';
import {bootstrapApplication} from './advanced/Advanced';
import Injected from './Injected';
import HandleChange from './HandleChange';
import StaticTypeSafetyAndCodeSplitting from './StaticTypeSafetyAndCodeSplitting/StaticTypeSafetyAndCodeSplitting';

import 'core-js/stable'
import '@formatjs/intl-pluralrules/polyfill'
import * as React from 'react'
import * as ReactDOM from 'react-dom'
import Timezone from './TimeZone'
import Messages from './Messages'
import Hooks from './Hooks'
import {bootstrapApplication} from './advanced/Advanced'
import Injected from './Injected'
import HandleChange from './HandleChange'
import StaticTypeSafetyAndCodeSplitting from './StaticTypeSafetyAndCodeSplitting/StaticTypeSafetyAndCodeSplitting'
import {App as Bug2727} from './Bug2727'
//polyfills
import '@formatjs/intl-getcanonicallocales/polyfill';
import '@formatjs/intl-locale/polyfill';
import '@formatjs/intl-getcanonicallocales/polyfill'
import '@formatjs/intl-locale/polyfill'

import '@formatjs/intl-pluralrules/polyfill';
import '@formatjs/intl-pluralrules/locale-data/en'; // locale-data for en
import '@formatjs/intl-pluralrules/polyfill'
import '@formatjs/intl-pluralrules/locale-data/en' // locale-data for en

import '@formatjs/intl-numberformat/polyfill';
import '@formatjs/intl-numberformat/locale-data/en'; // locale-data for en
import '@formatjs/intl-numberformat/polyfill'
import '@formatjs/intl-numberformat/locale-data/en' // locale-data for en

import '@formatjs/intl-datetimeformat/polyfill';
import '@formatjs/intl-datetimeformat/locale-data/en'; // locale-data for en
import '@formatjs/intl-datetimeformat/add-all-tz'; // Add ALL tz data
import '@formatjs/intl-datetimeformat/polyfill'
import '@formatjs/intl-datetimeformat/locale-data/en' // locale-data for en
import '@formatjs/intl-datetimeformat/add-all-tz' // Add ALL tz data

ReactDOM.render(<Timezone />, document.getElementById('timezone'));
ReactDOM.render(<Timezone />, document.getElementById('timezone'))

ReactDOM.render(<Messages />, document.getElementById('messages'));
ReactDOM.render(<Hooks />, document.getElementById('hooks'));
ReactDOM.render(<Messages />, document.getElementById('messages'))
ReactDOM.render(<Hooks />, document.getElementById('hooks'))

ReactDOM.render(<Injected />, document.getElementById('injected'));
ReactDOM.render(<HandleChange />, document.getElementById('handlechange'));
ReactDOM.render(<Injected />, document.getElementById('injected'))
ReactDOM.render(<HandleChange />, document.getElementById('handlechange'))

ReactDOM.render(
<StaticTypeSafetyAndCodeSplitting />,
document.getElementById('static-type-safety-and-code-splitting')
);
)

ReactDOM.render(<Bug2727 />, document.getElementById('bug2727'))

bootstrapApplication('en').then(app => {
ReactDOM.render(app, document.getElementById('advanced'));
});
ReactDOM.render(app, document.getElementById('advanced'))
})
95 changes: 37 additions & 58 deletions packages/react-intl/src/components/relative.tsx
Expand Up @@ -98,51 +98,6 @@ const SimpleFormattedRelativeTime: React.FC<
return <>{formattedRelativeTime}</>
}

function scheduleNextUpdate(
updateTimer: number | undefined,
updateIntervalInSeconds: number | undefined,
unit: RelativeTimeFormatSingularUnit | undefined,
currentValueInSeconds: number,
setUpdateTimer: (updateTimer: number) => void,
setCurrentValueInSeconds: (value: number) => void
) {
function clearUpdateTimer() {
clearTimeout(updateTimer)
}
clearTimeout(updateTimer)

// If there's no interval and we cannot increment this unit, do nothing
if (!updateIntervalInSeconds || !canIncrement(unit)) {
return clearUpdateTimer
}
// Figure out the next interesting time
const nextValueInSeconds = currentValueInSeconds - updateIntervalInSeconds
const nextUnit = selectUnit(nextValueInSeconds)
// We've reached the max auto incrementable unit, don't schedule another update
if (nextUnit === 'day') {
return clearUpdateTimer
}

const unitDuration = getDurationInSeconds(nextUnit)
const remainder = nextValueInSeconds % unitDuration
const prevInterestingValueInSeconds = nextValueInSeconds - remainder
const nextInterestingValueInSeconds =
prevInterestingValueInSeconds >= currentValueInSeconds
? prevInterestingValueInSeconds - unitDuration
: prevInterestingValueInSeconds
const delayInSeconds = Math.abs(
nextInterestingValueInSeconds - currentValueInSeconds
)

setUpdateTimer(
(setTimeout(
() => setCurrentValueInSeconds(nextInterestingValueInSeconds),
delayInSeconds * 1e3
) as unknown) as number
)
return clearUpdateTimer
}

const FormattedRelativeTime: React.FC<Props> = ({
value,
unit,
Expand All @@ -162,7 +117,7 @@ const FormattedRelativeTime: React.FC<Props> = ({
currentValueInSeconds,
setCurrentValueInSeconds,
] = React.useState<number>(0)
const [updateTimer, setUpdateTimer] = React.useState<number | undefined>()
let updateTimer: number

if (unit !== prevUnit || value !== prevValue) {
setPrevValue(value || 0)
Expand All @@ -172,18 +127,42 @@ const FormattedRelativeTime: React.FC<Props> = ({
)
}

React.useEffect(
() =>
scheduleNextUpdate(
updateTimer,
updateIntervalInSeconds,
unit,
currentValueInSeconds,
setUpdateTimer,
setCurrentValueInSeconds
),
[currentValueInSeconds, updateIntervalInSeconds, unit]
)
React.useEffect(() => {
function clearUpdateTimer() {
clearTimeout(updateTimer)
}
clearUpdateTimer()
// If there's no interval and we cannot increment this unit, do nothing
if (!updateIntervalInSeconds || !canIncrement(unit)) {
return clearUpdateTimer
}
// Figure out the next interesting time
const nextValueInSeconds = currentValueInSeconds - updateIntervalInSeconds
const nextUnit = selectUnit(nextValueInSeconds)
// We've reached the max auto incrementable unit, don't schedule another update
if (nextUnit === 'day') {
return clearUpdateTimer
}

const unitDuration = getDurationInSeconds(nextUnit)
const remainder = nextValueInSeconds % unitDuration
const prevInterestingValueInSeconds = nextValueInSeconds - remainder
const nextInterestingValueInSeconds =
prevInterestingValueInSeconds >= currentValueInSeconds
? prevInterestingValueInSeconds - unitDuration
: prevInterestingValueInSeconds
const delayInSeconds = Math.abs(
nextInterestingValueInSeconds - currentValueInSeconds
)

if (currentValueInSeconds !== nextInterestingValueInSeconds) {
updateTimer = (setTimeout(
() => setCurrentValueInSeconds(nextInterestingValueInSeconds),
delayInSeconds * 1e3
) as unknown) as number
}
return clearUpdateTimer
}, [currentValueInSeconds, updateIntervalInSeconds, unit])

let currentValue = value || 0
let currentUnit = unit
Expand Down

0 comments on commit bdc0586

Please sign in to comment.