You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native计时统计页报错:Cannot read property 'week' of undefined

React Native计时应用统计页面报错:Can’t read property 'week' of undefined

问题详情

开发React Native计时应用时,统计页面需展示计时器本周、本月及今日的启动时长,但运行时触发错误:Can’t read property 'week' of undefined,错误定位在Statistic模块。

安卓模拟器报错截图:
安卓模拟器报错截图

相关组件代码

Timer组件代码

import React, { useState, useEffect } from "react";
import { Image, View, Button, Text } from "react-native";
import TextStyle from "../constants/style/TextStyle";
import ProgressCircle from "react-native-progress-circle";
import { Vibration } from 'react-native';

const formatTime = (seconds) => {
  const minutes = Math.floor(seconds / 60);
  const remainingSeconds = seconds % 60;
  return `${minutes}:${remainingSeconds < 10 ? "0" : ""}${remainingSeconds}`;
};

const Timer = () => {
  const focusDuration = 30 * 60; // 30分钟(秒数)
  const breakDuration = 5 * 60; // 5分钟(秒数)
  const [isRunning, setIsRunning] = useState(false);
  const [isPaused, setIsPaused] = useState(false);
  const [isFocusTime, setIsFocusTime] = useState(true);
  const [currentPhase, setCurrentPhase] = useState('focus'); // 'focus' 或 'break'
  const [elapsedSeconds, setElapsedSeconds] = useState(0);
  const [remainingTime, setRemainingTime] = useState(focusDuration); // 初始值应为focusDuration

  // 存储计时器使用数据的状态
  const [usageData, setUsageData] = useState({
    week: 0, // 本周用时(秒)
    today: 0, // 今日用时(秒)
    month: 0, // 本月用时(秒)
  });

  useEffect(() => {
    let interval;

    if (isRunning && !isPaused) {
      interval = setInterval(() => {
        setElapsedSeconds((prevSeconds) => {
          if (prevSeconds >= remainingTime) {
            clearInterval(interval);
            setIsRunning(false);
            setIsPaused(false);

            if (currentPhase === 'focus') {
              setCurrentPhase('break');
              setElapsedSeconds(0);
              setRemainingTime(breakDuration);
              setIsFocusTime(false); // 休息阶段设置为false
            } else {
              setCurrentPhase('focus');
              setElapsedSeconds(0);
              setRemainingTime(focusDuration);
              setIsFocusTime(true); // 专注阶段设置为true
            }

            // 自动启动下一阶段计时器
            setIsRunning(true);

            // 休息阶段震动3次
            if (currentPhase === 'break') {
              for (let i = 0; i < 3; i++) {
                setTimeout(() => {
                  Vibration.vibrate(500);
                }, i * 1000);
              }
            }

            // 根据当前阶段更新使用数据
            setUsageData((prevData) => ({
              ...prevData,
              week: currentPhase === 'focus' ? prevData.week + focusDuration : prevData.week + breakDuration,
              today: currentPhase === 'focus' ? prevData.today + focusDuration : prevData.today + breakDuration,
              month: currentPhase === 'focus' ? prevData.month + focusDuration : prevData.month + breakDuration,
            }));

          } else {
            return prevSeconds + 1;
          }
        });
      }, 1000);
    } else {
      clearInterval(interval);
    }

    return () => {
      clearInterval(interval);
    };
  }, [isRunning, isPaused, currentPhase, remainingTime]);

  const handleStart = () => {
    setIsRunning(true);
    setIsPaused(false);
  };

  const handlePause = () => {
    setIsPaused(true);
  };

  const handleResume = () => {
    setIsPaused(false);
  };

  const handleCancel = () => {
    setIsRunning(false);
    setIsPaused(false);
    setElapsedSeconds(0);
  };

  const maxDuration = isFocusTime ? focusDuration : breakDuration;
  const animationPercentage = (elapsedSeconds / maxDuration) * 100;

  return (
    <View style={TextStyle.container}>
      <View style={TextStyle.threeLines} >
        <Image source={require('../assets/ThreeLines.png')} ></Image>
      </View>

      <View style={TextStyle.blackCircleImage} >
        <View style={TextStyle.outerCircle}>
          <ProgressCircle
            percent={100 - animationPercentage}
            radius={150}
            borderWidth={2}
            color="#fff"
            shadowColor='#000'
            bgColor="#000"
            rotation={90}
            clockwise={false}
          >
            <Text style={TextStyle.timeText}>
              {formatTime(maxDuration - elapsedSeconds)}
            </Text>
            <Text style={TextStyle.focusText}>
              {isFocusTime ? 'FOCUS' : 'BREAK'}
            </Text>
          </ProgressCircle>

        </View>
      </View>

      <View style={TextStyle.buttonStartView}>
        {!isRunning && (
          <Button color={'#fff'} title="START" onPress={handleStart} />
        )}
        {isRunning && !isPaused && (
          <>
            <Button color={'#fff'} title="PAUSE" onPress={handlePause} />
            <Button color={'#fff'} title="STOP" onPress={handleCancel} />
          </>
        )}
        {isRunning && isPaused && (
          <>
            <Button color={'#fff'} title="RESUME" onPress={handleResume} />
            <Button color={'#fff'} title="STOP" onPress={handleCancel} />
          </>
        )}
      </View>

      <View style={TextStyle.bottomText}>
        <Text style={TextStyle.bottomTimeText}>
          <Text style={TextStyle.smallText}>Focus{'\n'}</Text>
          {formatTime(focusDuration)}{'\n'}
          <Text style={TextStyle.smallText}>Break{'\n'}</Text>
          {formatTime(breakDuration)}
        </Text>
      </View>
      {/* <Stats usageData={usageData} /> */}

    </View>
  )
}

export default Timer 

Statistic组件代码

import React from 'react';
import { View, Text } from 'react-native';

const formatTimeInHoursMinutes = (seconds) => {
  const hours = Math.floor(seconds / 3600);
  const minutes = Math.floor((seconds % 3600) / 60);
  return `${hours}h ${minutes}m`;
};

const Statistic = ({ usageData }) => {
    return (
      <View>
        <Text>Stats</Text>
        <Text style={{ color: 'black' }}>This Week: {formatTimeInHoursMinutes(usageData.week)} </Text>
        <Text style={{ color: 'black' }}>Today: {formatTimeInHoursMinutes(usageData.today)} </Text>
        <Text style={{ color: 'black' }}>This Month: {formatTimeInHoursMinutes(usageData.month)} </Text>
      </View>
    );
  }
  

export default Statistic

Stats组件代码

import React from 'react';
import { View } from 'react-native';
import Statistic from './Statistic';

const Stats = ({ usageData }) => {
  return (
    <View>
     <Statistic usageData={usageData} />
    </View>
  );
}

export default Stats;

问题原因及解决方法

核心原因

Statistic组件中直接访问usageData.week,但usageData未被正确传入组件,导致其值为undefined,触发属性读取错误。

具体修复步骤

  1. 启用Timer组件中的Stats引用
    取消Timer组件中<Stats usageData={usageData} />的注释,确保统计组件能接收到Timer中维护的usageData状态:

    // Timer组件return部分取消注释
    <Stats usageData={usageData} />
    
  2. 添加参数默认值防护
    在Statistic组件中为usageData设置默认值,避免未传入或传入undefined时报错:

    const Statistic = ({ usageData = { week: 0, today: 0, month: 0 } }) => {
      // 原有组件逻辑保持不变
    }
    

    或者使用可选链操作符做安全访问:

    <Text style={{ color: 'black' }}>This Week: {formatTimeInHoursMinutes(usageData?.week || 0)} </Text>
    <Text style={{ color: 'black' }}>Today: {formatTimeInHoursMinutes(usageData?.today || 0)} </Text>
    <Text style={{ color: 'black' }}>This Month: {formatTimeInHoursMinutes(usageData?.month || 0)} </Text>
    
  3. 路由跳转场景的数据传递检查
    如果Stats页面是通过路由跳转打开的(比如使用React Navigation),需确保跳转时正确传递usageData:

    • 跳转时传递数据:
      navigation.navigate('Stats', { usageData: usageData })
      
    • 在Stats页面接收数据:
      const Stats = ({ route }) => {
        const { usageData = { week: 0, today: 0, month: 0 } } = route.params || {};
        return (
          <View>
            <Statistic usageData={usageData} />
          </View>
        );
      }
      

额外优化建议

  • 修正统计逻辑准确性:当前usageData仅在阶段结束时一次性增加固定时长,若用户中途暂停或取消计时器,统计数据会失真。建议改为每秒累计实际运行时长:
    // 在Timer组件的useEffect定时器中,更新elapsedSeconds时同步更新usageData
    setElapsedSeconds((prevSeconds) => {
      // 同步更新统计数据
      setUsageData(prev => ({
        ...prev,
        week: prev.week + 1,
        today: prev.today + 1,
        month: prev.month + 1
      }));
      return prevSeconds + 1;
    });
    
  • 持久化统计数据:使用AsyncStorage或其他存储方案保存usageData,避免应用重启后数据丢失。

内容的提问来源于stack exchange,提问作者Ismail Masood Orakzai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 07:10:57