React 原生 DatePicker 提供 android 错误

问题描述 投票:0回答:1

我有一个无法理解的错误,因为该错误仅发生在 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>
  )
}



我尝试调试获取上下文,但我是新开发者

reactjs react-native datepicker
1个回答
0
投票

我也遇到了这个问题,并在这个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>
    );
}
© www.soinside.com 2019 - 2024. All rights reserved.