我有一个无法理解的错误,因为该错误仅发生在 Android 上 错误如下:
错误类型错误:无法读取未设置的属性“日历”
此错误位于: 在 CalendarHeader 中(由 DatePickerBody 创建) 在 RCT 查看中 未知(由 DatePickerBody 创建) 在提供程序中(由 DatePickerBody 创建) 在 DatePickerBody 中(由 DatePickerExample 创建) 未知中 在重置状态中 在删除滚动中 在RCTView中(由View创建) 在视图中(由 Animated(View) 创建) 在 Animated(View) 中(由 PopperContent 创建) 在 ResetPresence 中(由 PopperContent 创建) 在波普尔内容中 在RCTView中(由Stack创建) 在堆里 未知中 未知(由 Animate 创建) 在动画中 未知中 在RCTView中(由Stack创建) 在堆里 在提供者中(由 PopoverRepropagateContext 创建) 在提供者中(由 PopoverRepropagateContext 创建) 在 PopoverRepropagateContext (由 PopoverContentPortal 创建) 主题(由 PopoverContentPortal 创建) 在RCTView中(由YStack创建) 在YStack(由Portal创建)中 在 PortalHostComponent 中(由 PortalProviderComponent 创建) 在 PortalProviderComponent 中(由 TamaguiProvider 创建) 在主题中(由 ThemeProvider 创建) 在 ThemeProvider 中(由 TamaguiProvider 创建) 在 Provider 中(由 TamaguiProvider 创建) 在 TamaguiProvider 中(由 TamaguiProvider 创建) 在 TamaguiProvider 中(由 RootLayoutNav 创建) 在 RootLayoutNav (由 RootLayout 创建) 在根布局中 在尝试 未知中 未知(由 Route() 创建) 待处理(由 Route() 创建) 在路线中(由 Route() 创建) 在 Route() (由 ContextNavigator 创建) 在 RNCSafeAreaProvider 中(由 SafeAreaProvider 创建) 在 SafeAreaProvider 中(由包装器创建) 在包装器中(由 ContextNavigator 创建) 在 EnsureSingleNavigator 中 在 BaseNavigationContainer 中 在主题提供者中 在NavigationContainerInner(由ContextNavigator创建)中 在 ContextNavigator(由 ExpoRoot 创建)中 在ExpoRoot(由App创建) 在应用程序中(由 ErrorOverlay 创建) 在 ErrorToastContainer 中(由 ErrorOverlay 创建) 在 ErrorOverlay 中(由 withDevTools(ErrorOverlay) 创建) 在 withDevTools(ErrorOverlay) 中 在RCTView中(由View创建) 在视图中(由 AppContainer 创建) 在RCTView中(由View创建) 在视图中(由 AppContainer 创建) 在应用程序容器中 在main(RootComponent)中,js引擎:hermes
import { useDatePickerContext } from '@rehookify/datepicker'
import type { DPDay } from '@rehookify/datepicker'
import { ChevronLeft, ChevronRight } from '@tamagui/lucide-icons'
import { useEffect, useMemo, useState } from 'react'
import { AnimatePresence, Button, View } from 'tamagui'
import { DatePicker } from './dateParts'
import {
DatePickerInput,
SizableText,
YearPicker,
YearRangeSlider,
swapOnClick,
useHeaderType,
HeaderTypeProvider,
MonthPicker,
} from './dateParts'
function CalendarHeader() {
const {
data: { calendars },
propGetters: { subtractOffset },
} = useDatePickerContext()
const { type: header, setHeader } = useHeaderType()
const { year, month } = calendars[0]
if (header === 'year') {
return <YearRangeSlider />
}
if (header === 'month') {
return (
<SizableText width="100%" ta="center" userSelect="auto" tabIndex={0} size="$8">
Select a month
</SizableText>
)
}
return (
<View
flexDirection="row"
width="100%"
height={50}
alignItems="center"
justifyContent="space-between"
>
<Button circular size="$4" {...swapOnClick(subtractOffset({ months: 1 }))}>
<Button.Icon scaleIcon={1.5}>
<ChevronLeft />
</Button.Icon>
</Button>
<View flexDirection="column" height={50} alignItems="center">
<SizableText
onPress={() => setHeader('year')}
userSelect="auto"
tabIndex={0}
size="$4"
cursor="pointer"
color="$color11"
hoverStyle={{
color: '$color12',
}}
>
{year}
</SizableText>
<SizableText
onPress={() => setHeader('month')}
userSelect="auto"
cursor="pointer"
tabIndex={0}
size="$6"
color="$gray12"
fontWeight="600"
lineHeight="$1"
hoverStyle={{
color: '$gray10',
}}
>
{month}
</SizableText>
</View>
<Button circular size="$4" {...swapOnClick(subtractOffset({ months: -1 }))}>
<Button.Icon scaleIcon={1.5}>
<ChevronRight />
</Button.Icon>
</Button>
</View>
)
}
export function useDateAnimation({ listenTo }: { listenTo: 'year' | 'month' | 'years' }) {
const {
data: { years, calendars },
} = useDatePickerContext()
const [currentMonth, setCurrentMonth] = useState<string | null>(null)
const [currentYear, setCurrentYear] = useState<string | null>(null)
const [currentYearsSum, setCurrentYearsSum] = useState<number | null>(null)
const sumYears = () => {
return years.reduce((acc, date) => acc + date.year, 0)
}
useEffect(() => {
if (listenTo === 'years') {
if (currentYearsSum !== sumYears()) {
setCurrentYearsSum(sumYears())
}
}
}, [years, currentYearsSum])
useEffect(() => {
if (listenTo === 'month') {
if (currentMonth !== calendars[0].month) {
setCurrentMonth(calendars[0].month)
}
}
}, [calendars[0][listenTo], currentMonth])
useEffect(() => {
if (listenTo === 'year') {
if (currentYear !== calendars[0].year) {
setCurrentYear(calendars[0].year)
}
}
}, [calendars[0][listenTo], currentYear])
const prevNextAnimation = () => {
if (listenTo === 'years') {
if (currentYearsSum === null) return { enterStyle: { opacity: 0 } }
return {
enterStyle: { opacity: 0, x: sumYears() < currentYearsSum ? -15 : 15 },
exitStyle: { opacity: 0, x: sumYears() < currentYearsSum ? -15 : 15 },
}
}
if (listenTo === 'month') {
if (currentMonth === null) return { enterStyle: { opacity: 0 } }
const newDate = new Date(`${calendars[0][listenTo]} 1, ${calendars[0].year}`)
const currentDate = new Date(`${currentMonth} 1, ${calendars[0].year}`)
if (currentMonth === 'December' && calendars[0].month === 'January') {
return {
enterStyle: { opacity: 0, x: 15 },
exitStyle: { opacity: 0, x: 15 },
}
}
if (currentMonth === 'January' && calendars[0].month === 'December') {
return {
enterStyle: { opacity: 0, x: -15 },
exitStyle: { opacity: 0, x: -15 },
}
}
return {
enterStyle: { opacity: 0, x: newDate < currentDate ? -15 : 15 },
exitStyle: { opacity: 0, x: newDate < currentDate ? -15 : 15 },
}
}
if (listenTo === 'year') {
if (currentYear === null) return { enterStyle: { opacity: 0 } }
const newDate = new Date(`${calendars[0].month} 1, ${calendars[0].year}`)
const currentDate = new Date(`${calendars[0].month} 1, ${currentYear}`)
return {
enterStyle: { opacity: 0, x: newDate < currentDate ? -15 : 15 },
exitStyle: { opacity: 0, x: newDate < currentDate ? -15 : 15 },
}
}
}
return {
prevNextAnimation,
prevNextAnimationKey: listenTo === 'years' ? sumYears() : calendars[0][listenTo],
}
}
function DayPicker() {
const {
data: { calendars, weekDays },
propGetters: { dayButton },
} = useDatePickerContext()
const { days } = calendars[0]
const { prevNextAnimation, prevNextAnimationKey } = useDateAnimation({
listenTo: 'month',
})
// divide days array into sub arrays that each has 7 days, for better stylings
const subDays = useMemo(
() =>
days.reduce((acc, day, i) => {
if (i % 7 === 0) {
acc.push([])
}
acc[acc.length - 1].push(day)
return acc
}, [] as DPDay[][]),
[days]
)
return (
<AnimatePresence key={prevNextAnimationKey}>
<View animation="medium" {...prevNextAnimation()}>
<View flexDirection="row" gap="$1">
{weekDays.map((day) => (
<SizableText key={day} ta="center" width={45} size="$4">
{day}
</SizableText>
))}
</View>
<View flexDirection="column" gap="$1" flexWrap="wrap">
{subDays.map((days) => {
return (
<View flexDirection="row" key={days[0].$date.toString()} gap="$1">
{days.map((d) => (
<Button
key={d.$date.toString()}
chromeless
circular
padding={0}
width={45}
{...swapOnClick(dayButton(d))}
backgroundColor={d.selected ? '$background' : 'transparent'}
themeInverse={d.selected}
disabled={!d.inCurrentMonth}
>
<Button.Text
color={
d.selected ? '$gray12' : d.inCurrentMonth ? '$gray11' : '$gray6'
}
>
{d.day}
</Button.Text>
</Button>
))}
</View>
)
})}
</View>
</View>
</AnimatePresence>
)
}
function DatePickerBody() {
const [header, setHeader] = useState<'day' | 'month' | 'year'>('day')
return (
<HeaderTypeProvider type={header} setHeader={setHeader}>
<View flexDirection="column" alignItems="center" gap="$2.5" maxWidth={325}>
<CalendarHeader />
{header === 'month' && <MonthPicker onChange={() => setHeader('day')} />}
{header === 'year' && <YearPicker onChange={() => setHeader('day')} />}
{header === 'day' && <DayPicker />}
</View>
</HeaderTypeProvider>
)
}
/** ------ EXAMPLE ------ */
export function DatePickerExample() {
const [selectedDates, onDatesChange] = useState<Date[]>([])
const [open, setOpen] = useState(false)
useEffect(() => {
setOpen(false)
}, [selectedDates])
return (
<DatePicker
open={open}
onOpenChange={setOpen}
config={{
selectedDates,
onDatesChange,
calendar: {
startDay: 1,
},
}}
>
<DatePicker.Trigger asChild>
<DatePickerInput
placeholder="Select Date"
value={selectedDates[0]?.toDateString() || ''}
onReset={() => onDatesChange([])}
onButtonPress={() => setOpen(true)}
/>
</DatePicker.Trigger>
<DatePicker.Content>
<DatePicker.Content.Arrow />
<DatePickerBody />
</DatePicker.Content>
</DatePicker>
)
}
我尝试调试获取上下文,但我是新开发者
我也遇到了这个问题,并在这个github票证中添加了一个解决方法,即,将
<DatePickerBody />
包装在另一个<DatePickerProvider>
中:
export function DatePickerExample() {
const [selectedDates, onDatesChange] = useState<Date[]>([]);
const [open, setOpen] = useState(false);
+ const dpConfig = {
+ selectedDates,
+ onDatesChange,
+ calendar: {
+ startDay: 1 as const,
+ },
+ };
useEffect(() => {
setOpen(false);
}, [selectedDates]);
return (
<DatePicker
keepChildrenMounted
open={open}
onOpenChange={setOpen}
- config={{
- selectedDates,
- onDatesChange,
- calendar: {
- startDay: 1,
- },
- }}
+ config={dpConfig}
>
<DatePicker.Trigger asChild>
<DatePickerInput
placeholder="Select Date"
value={selectedDates[0]?.toDateString() || ''}
onReset={() => onDatesChange([])}
onButtonPress={() => setOpen(true)}
/>
</DatePicker.Trigger>
<DatePicker.Content>
<DatePicker.Content.Arrow />
- <DatePickerBody />
+ <DatePickerProvider config={dpConfig}>
+ <DatePickerBody />
+ </DatePickerProvider>
</DatePicker.Content>
</DatePicker>
);
}