Don't use property value for key construction. (#2317)
* Don't use property value for key construction. * Use value from props as current value in `DateRange` component: - this allows to update it properly in the dialog when the value on the server is updated - current value is stored in `PropertyValueElement` similar to other property value components - fixed stale closure in `Modal` component - unit tests for `DateRange` updated (wrapper with current value created)
This commit is contained in:
parent
ed33918d0a
commit
46f4185d30
5 changed files with 59 additions and 49 deletions
|
@ -135,10 +135,9 @@ const CardDetailProperties = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<div className='octo-propertylist CardDetailProperties'>
|
<div className='octo-propertylist CardDetailProperties'>
|
||||||
{board.fields.cardProperties.map((propertyTemplate: IPropertyTemplate) => {
|
{board.fields.cardProperties.map((propertyTemplate: IPropertyTemplate) => {
|
||||||
const propertyValue = card.fields.properties[propertyTemplate.id]
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={propertyTemplate.id + '-' + propertyTemplate.type + '-' + propertyValue}
|
key={propertyTemplate.id + '-' + propertyTemplate.type}
|
||||||
className='octo-propertyrow'
|
className='octo-propertyrow'
|
||||||
>
|
>
|
||||||
{props.readonly && <div className='octo-propertyname octo-propertyname--readonly'>{propertyTemplate.name}</div>}
|
{props.readonly && <div className='octo-propertyname octo-propertyname--readonly'>{propertyTemplate.name}</div>}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import React, {useRef, useEffect} from 'react'
|
import React, {useRef, useEffect, useCallback} from 'react'
|
||||||
|
|
||||||
import IconButton from '../widgets/buttons/iconButton'
|
import IconButton from '../widgets/buttons/iconButton'
|
||||||
import CloseIcon from '../widgets/icons/close'
|
import CloseIcon from '../widgets/icons/close'
|
||||||
|
@ -17,20 +17,19 @@ const Modal = (props: Props): JSX.Element => {
|
||||||
|
|
||||||
const {position, onClose, children} = props
|
const {position, onClose, children} = props
|
||||||
|
|
||||||
const closeOnBlur = (e: Event) => {
|
const closeOnBlur = useCallback((e: Event) => {
|
||||||
if (e.target && node.current?.contains(e.target as Node)) {
|
if (e.target && node.current?.contains(e.target as Node)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}, [onClose])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.addEventListener('click', closeOnBlur, true)
|
document.addEventListener('click', closeOnBlur, true)
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('click', closeOnBlur, true)
|
document.removeEventListener('click', closeOnBlur, true)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [closeOnBlur])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import React from 'react'
|
import React, {useState} from 'react'
|
||||||
import {render} from '@testing-library/react'
|
import {render} from '@testing-library/react'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import {IntlProvider} from 'react-intl'
|
import {IntlProvider} from 'react-intl'
|
||||||
|
@ -17,6 +17,27 @@ const June15 = new Date(Date.UTC(new Date().getFullYear(), 5, 15, 12))
|
||||||
const June15Local = new Date(new Date().getFullYear(), 5, 15, 12)
|
const June15Local = new Date(new Date().getFullYear(), 5, 15, 12)
|
||||||
const June20 = new Date(Date.UTC(new Date().getFullYear(), 5, 20, 12))
|
const June20 = new Date(Date.UTC(new Date().getFullYear(), 5, 20, 12))
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
initialValue?: string
|
||||||
|
showEmptyPlaceholder?: boolean
|
||||||
|
onChange?: (value: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const DateRangeWrapper = (props: Props): JSX.Element => {
|
||||||
|
const [value, setValue] = useState(props.initialValue || '')
|
||||||
|
return (
|
||||||
|
<DateRange
|
||||||
|
className='octo-propertyvalue'
|
||||||
|
value={value}
|
||||||
|
showEmptyPlaceholder={props.showEmptyPlaceholder}
|
||||||
|
onChange={(newValue) => {
|
||||||
|
setValue(newValue)
|
||||||
|
props.onChange?.(newValue)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
describe('components/properties/dateRange', () => {
|
describe('components/properties/dateRange', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Quick fix to disregard console error when unmounting a component
|
// Quick fix to disregard console error when unmounting a component
|
||||||
|
@ -26,9 +47,8 @@ describe('components/properties/dateRange', () => {
|
||||||
|
|
||||||
test('returns default correctly', () => {
|
test('returns default correctly', () => {
|
||||||
const component = wrapIntl(
|
const component = wrapIntl(
|
||||||
<DateRange
|
<DateRangeWrapper
|
||||||
className='octo-propertyvalue'
|
initialValue=''
|
||||||
value={''}
|
|
||||||
onChange={jest.fn()}
|
onChange={jest.fn()}
|
||||||
/>,
|
/>,
|
||||||
)
|
)
|
||||||
|
@ -40,9 +60,8 @@ describe('components/properties/dateRange', () => {
|
||||||
test('returns local correctly - es local', () => {
|
test('returns local correctly - es local', () => {
|
||||||
const component = (
|
const component = (
|
||||||
<IntlProvider locale='es'>
|
<IntlProvider locale='es'>
|
||||||
<DateRange
|
<DateRangeWrapper
|
||||||
className='octo-propertyvalue'
|
initialValue={June15Local.getTime().toString()}
|
||||||
value={June15Local.getTime().toString()}
|
|
||||||
onChange={jest.fn()}
|
onChange={jest.fn()}
|
||||||
/>
|
/>
|
||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
|
@ -57,9 +76,8 @@ describe('components/properties/dateRange', () => {
|
||||||
test('handles calendar click event', () => {
|
test('handles calendar click event', () => {
|
||||||
const callback = jest.fn()
|
const callback = jest.fn()
|
||||||
const component = wrapIntl(
|
const component = wrapIntl(
|
||||||
<DateRange
|
<DateRangeWrapper
|
||||||
className='octo-propertyvalue'
|
initialValue=''
|
||||||
value={''}
|
|
||||||
showEmptyPlaceholder={true}
|
showEmptyPlaceholder={true}
|
||||||
onChange={callback}
|
onChange={callback}
|
||||||
/>,
|
/>,
|
||||||
|
@ -84,9 +102,8 @@ describe('components/properties/dateRange', () => {
|
||||||
test('handles setting range', () => {
|
test('handles setting range', () => {
|
||||||
const callback = jest.fn()
|
const callback = jest.fn()
|
||||||
const component = wrapIntl(
|
const component = wrapIntl(
|
||||||
<DateRange
|
<DateRangeWrapper
|
||||||
className='octo-propertyvalue'
|
initialValue={''}
|
||||||
value={''}
|
|
||||||
showEmptyPlaceholder={true}
|
showEmptyPlaceholder={true}
|
||||||
onChange={callback}
|
onChange={callback}
|
||||||
/>,
|
/>,
|
||||||
|
@ -121,9 +138,8 @@ describe('components/properties/dateRange', () => {
|
||||||
test('handle clear', () => {
|
test('handle clear', () => {
|
||||||
const callback = jest.fn()
|
const callback = jest.fn()
|
||||||
const component = wrapIntl(
|
const component = wrapIntl(
|
||||||
<DateRange
|
<DateRangeWrapper
|
||||||
className='octo-propertyvalue'
|
initialValue={June15Local.getTime().toString()}
|
||||||
value={June15Local.getTime().toString()}
|
|
||||||
onChange={callback}
|
onChange={callback}
|
||||||
/>,
|
/>,
|
||||||
)
|
)
|
||||||
|
@ -146,9 +162,8 @@ describe('components/properties/dateRange', () => {
|
||||||
test('set via text input', () => {
|
test('set via text input', () => {
|
||||||
const callback = jest.fn()
|
const callback = jest.fn()
|
||||||
const component = wrapIntl(
|
const component = wrapIntl(
|
||||||
<DateRange
|
<DateRangeWrapper
|
||||||
className='octo-propertyvalue'
|
initialValue={'{"from": ' + June15.getTime().toString() + ',"to": ' + June20.getTime().toString() + '}'}
|
||||||
value={'{"from": ' + June15.getTime().toString() + ',"to": ' + June20.getTime().toString() + '}'}
|
|
||||||
onChange={callback}
|
onChange={callback}
|
||||||
/>,
|
/>,
|
||||||
)
|
)
|
||||||
|
@ -183,9 +198,8 @@ describe('components/properties/dateRange', () => {
|
||||||
|
|
||||||
const component = (
|
const component = (
|
||||||
<IntlProvider locale='es'>
|
<IntlProvider locale='es'>
|
||||||
<DateRange
|
<DateRangeWrapper
|
||||||
className='octo-propertyvalue'
|
initialValue={'{"from": ' + June15.getTime().toString() + ',"to": ' + June20.getTime().toString() + '}'}
|
||||||
value={'{"from": ' + June15.getTime().toString() + ',"to": ' + June20.getTime().toString() + '}'}
|
|
||||||
onChange={callback}
|
onChange={callback}
|
||||||
/>
|
/>
|
||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
|
@ -218,9 +232,8 @@ describe('components/properties/dateRange', () => {
|
||||||
test('cancel set via text input', () => {
|
test('cancel set via text input', () => {
|
||||||
const callback = jest.fn()
|
const callback = jest.fn()
|
||||||
const component = wrapIntl(
|
const component = wrapIntl(
|
||||||
<DateRange
|
<DateRangeWrapper
|
||||||
className='octo-propertyvalue'
|
initialValue={'{"from": ' + June15.getTime().toString() + ',"to": ' + June20.getTime().toString() + '}'}
|
||||||
value={'{"from": ' + June15.getTime().toString() + ',"to": ' + June20.getTime().toString() + '}'}
|
|
||||||
onChange={callback}
|
onChange={callback}
|
||||||
/>,
|
/>,
|
||||||
)
|
)
|
||||||
|
@ -248,9 +261,8 @@ describe('components/properties/dateRange', () => {
|
||||||
test('handles `Today` button click event', () => {
|
test('handles `Today` button click event', () => {
|
||||||
const callback = jest.fn()
|
const callback = jest.fn()
|
||||||
const component = wrapIntl(
|
const component = wrapIntl(
|
||||||
<DateRange
|
<DateRangeWrapper
|
||||||
className='octo-propertyvalue'
|
initialValue={''}
|
||||||
value={''}
|
|
||||||
showEmptyPlaceholder={true}
|
showEmptyPlaceholder={true}
|
||||||
onChange={callback}
|
onChange={callback}
|
||||||
/>,
|
/>,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import React, {useState} from 'react'
|
import React, {useMemo, useState} from 'react'
|
||||||
import {useIntl} from 'react-intl'
|
import {useIntl} from 'react-intl'
|
||||||
import {DateUtils} from 'react-day-picker'
|
import {DateUtils} from 'react-day-picker'
|
||||||
import MomentLocaleUtils from 'react-day-picker/moment'
|
import MomentLocaleUtils from 'react-day-picker/moment'
|
||||||
|
@ -50,6 +50,10 @@ export function createDatePropertyFromString(initialValue: string) : DatePropert
|
||||||
return dateProperty
|
return dateProperty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function datePropertyToString(dateProperty: DateProperty): string {
|
||||||
|
return dateProperty.from || dateProperty.to ? JSON.stringify(dateProperty) : ''
|
||||||
|
}
|
||||||
|
|
||||||
const loadedLocales: Record<string, moment.Locale> = {}
|
const loadedLocales: Record<string, moment.Locale> = {}
|
||||||
|
|
||||||
function DateRange(props: Props): JSX.Element {
|
function DateRange(props: Props): JSX.Element {
|
||||||
|
@ -68,7 +72,7 @@ function DateRange(props: Props): JSX.Element {
|
||||||
return new Date(date).getTimezoneOffset() * 60 * 1000
|
return new Date(date).getTimezoneOffset() * 60 * 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
const [dateProperty, setDateProperty] = useState<DateProperty>(createDatePropertyFromString(value as string))
|
const dateProperty = useMemo(() => createDatePropertyFromString(value as string), [value])
|
||||||
const [showDialog, setShowDialog] = useState(false)
|
const [showDialog, setShowDialog] = useState(false)
|
||||||
|
|
||||||
// Keep dateProperty as UTC,
|
// Keep dateProperty as UTC,
|
||||||
|
@ -127,7 +131,7 @@ function DateRange(props: Props): JSX.Element {
|
||||||
rangeUTC.to -= dateProperty.includeTime ? 0 : timeZoneOffset(rangeUTC.to)
|
rangeUTC.to -= dateProperty.includeTime ? 0 : timeZoneOffset(rangeUTC.to)
|
||||||
}
|
}
|
||||||
|
|
||||||
setDateProperty(rangeUTC)
|
onChange(datePropertyToString(rangeUTC))
|
||||||
setFromInput(getDisplayDate(range.from ? new Date(range.from) : undefined))
|
setFromInput(getDisplayDate(range.from ? new Date(range.from) : undefined))
|
||||||
setToInput(getDisplayDate(range.to ? new Date(range.to) : undefined))
|
setToInput(getDisplayDate(range.to ? new Date(range.to) : undefined))
|
||||||
}
|
}
|
||||||
|
@ -141,16 +145,7 @@ function DateRange(props: Props): JSX.Element {
|
||||||
}
|
}
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
// not actually setting here,
|
onChange(datePropertyToString(dateProperty))
|
||||||
// but using to retreive the current state
|
|
||||||
setDateProperty((current) => {
|
|
||||||
if (current && current.from) {
|
|
||||||
onChange(JSON.stringify(current))
|
|
||||||
} else {
|
|
||||||
onChange('')
|
|
||||||
}
|
|
||||||
return {...current}
|
|
||||||
})
|
|
||||||
setShowDialog(false)
|
setShowDialog(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -171,7 +171,12 @@ const PropertyValueElement = (props:Props): JSX.Element => {
|
||||||
className='octo-propertyvalue'
|
className='octo-propertyvalue'
|
||||||
value={value.toString()}
|
value={value.toString()}
|
||||||
showEmptyPlaceholder={showEmptyPlaceholder}
|
showEmptyPlaceholder={showEmptyPlaceholder}
|
||||||
onChange={(newValue) => mutator.changePropertyValue(card, propertyTemplate.id, newValue)}
|
onChange={(newValue) => {
|
||||||
|
if (value !== newValue) {
|
||||||
|
setValue(newValue)
|
||||||
|
mutator.changePropertyValue(card, propertyTemplate.id, newValue)
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
} else if (propertyTemplate.type === 'url') {
|
} else if (propertyTemplate.type === 'url') {
|
||||||
|
|
Loading…
Reference in a new issue