如何使用React Hooks在首次渲染时使用道具加载子组件

时间:2019-09-19 12:28:29

标签: reactjs react-native redux react-redux

在我的React Native项目中,我在功能组件中使用React Hooks来设置Redux存储上的状态。然后,我试图通过名为prop

timestamp传递给子组件的存储中的值

问题是,子组件利用DateTimePicker组件(来自react-native-modal-datetime-picker)的优势,并且在最初呈现timestamp道具时未定义,这会导致其出错。结果可以通过以下控制台日志查看:

undefined
undefined
Object {
  "date": "2019-08-20",
  "full": 2019-08-20T12:21:52.874Z,
  "time": "01:21:52",
}
Object {
  "date": "20.08.2019",
  "time": "01:21 pm",
}

该组件被渲染两次。首先,道具没有定义,然后填充。

如何强制在第一个渲染之前加载props

我不想直接在子组件中调用状态,因为它将在各种用例中重复使用。

这是父组件:

import React, { useEffect } from 'react';
import { View } from 'react-native';
import { useSelector, useDispatch } from 'react-redux';
import DateInput from '../../../components/DateTimeInput';
import * as actions from '../../../redux/actions';
import moment from 'moment';

const ReportParamsScreen = props => {
    const report        = useSelector(state => state.report);
    const dispatch = useDispatch();

    useEffect(() => { 
        dispatch(actions.setReportParams({ 
            start_date  : moment().subtract(30, "days"),
            end_date    : moment()
        }))
    }, [dispatch]);

    return (
        <View style={styles.row}>
            <View style={styles.column}>
                <DateInput 
                    mode="date"
                    timestamp={report.params.start_date}
                />
            </View>
            <View style={styles.column}>
                <DateInput 
                    mode="date"
                    timestamp={report.params.start_date}
                />
            </View>
        </View>
    )
};

export default ReportParamsScreen;

这是子组件:

import React, { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { Body, Button, Text, Icon } from "native-base";
import DateTimePicker from "react-native-modal-datetime-picker";

const DateInput = props => {
    const [isPickerVisible, setIsPickerVisible] = useState(false);

    const showDateTimePicker = () => {
        setIsPickerVisible(true);
    };

    const hideDateTimePicker = () => {
        setIsPickerVisible(false);
    };

    const handleDateTimePicked = dateTime => {
        console.log(dateTime)
        hideDateTimePicker();
    };

    console.log(props.timestamp.value);
    console.log(props.timestamp.labels); // These are the logs referened above.

    return (
        <Body>
            <Button transparent onPress={showDateTimePicker}>
                <Icon
                    type="MaterialIcons"
                    name={props.mode == 'time' ? 'access-time' : 'date-range' }
                />
                <Text>{props.timestamp.labels[props.mode]}</Text>
            </Button>
            <DateTimePicker
                date={props.timestamp.value.date}
                mode={props.mode}
                isVisible={isPickerVisible}
                onConfirm={handleDateTimePicked}
                onCancel={hideDateTimePicker}
            />
        </Body>
    );
}

export default DateInput;

2 个答案:

答案 0 :(得分:2)

在这种情况下,您有两种解决方案,两者都将取决于对您或您的项目更有利的事物。

第一个解决方案是将start_dateend_date设置为关于Report的化简器的初始状态,但是我不知道这对您的业务/规则而言不是最佳解决方案,但是解决方案“更轻松”。

import React, { useEffect } from 'react';
import { View } from 'react-native';
import { useSelector, useDispatch } from 'react-redux';
import DateInput from '../../../components/DateTimeInput';
import * as actions from '../../../redux/actions';
import moment from 'moment';

const ReportParamsScreen = props => {
    const report        = useSelector(state => state.report);
    const dispatch = useDispatch();

    useEffect(() => { 
        dispatch(actions.setReportParams({ 
            start_date  : moment().subtract(30, "days"),
            end_date    : moment()
        }))
    }, [dispatch]);

    if (!report.params.start_date) {
      return null
    }

    return (
        <View style={styles.row}>
            <View style={styles.column}>
                <DateInput 
                    mode="date"
                    timestamp={report.params.start_date}
                />
            </View>
            <View style={styles.column}>
                <DateInput 
                    mode="date"
                    timestamp={report.params.start_date}
                />
            </View>
        </View>
    )
};

export default ReportParamsScreen;

第二个解决方案是验证report.params.start_date上的ReportParamsScreen是否未定义,如果为true,则在日期为null时返回组件的null或返回表示为Loading的组件,是个好习惯。

示例:

import React, { useEffect } from 'react';
import { View } from 'react-native';
import { useSelector, useDispatch } from 'react-redux';
import DateInput from '../../../components/DateTimeInput';
import * as actions from '../../../redux/actions';
import moment from 'moment';

const ReportParamsScreen = props => {
    const report        = useSelector(state => state.report);
    const dispatch = useDispatch();

    useEffect(() => { 
        dispatch(actions.setReportParams({ 
            start_date  : moment().subtract(30, "days"),
            end_date    : moment()
        }))
    }, [dispatch]);

    if (!report.params.start_date) {
      return <Loading />
    }

    return (
        <View style={styles.row}>
            <View style={styles.column}>
                <DateInput 
                    mode="date"
                    timestamp={report.params.start_date}
                />
            </View>
            <View style={styles.column}>
                <DateInput 
                    mode="date"
                    timestamp={report.params.start_date}
                />
            </View>
        </View>
    )
};

export default ReportParamsScreen;

发生这种情况是因为useEffect仅在渲染之后执行。

参考:

  

useEffect有什么作用?通过使用这个Hook,您可以告诉React您的组件在渲染后需要做一些事情。 React将记住您传递的函数(我们将其称为“效果”),并在执行DOM更新后稍后调用它。为此,我们可以设置文档标题,但也可以执行数据获取或调用其他命令性API。

链接:https://reactjs.org/docs/hooks-effect.html

答案 1 :(得分:0)

您可以为默认的reducer设置初始状态,初始状态可能如下所示

const initialState = {
  report: {
     params: { 
      start_date: moment().subtract(30, "days"),
      end_date: moment()
    }
  }
}

function rootReducer(state = initialState, action){
   //...
}

在父组件中,reportundefined是因为没有初始状态,该状态仅在第一次渲染后才设置,因为useEffect仅在渲染后被调用

或者您可以为DateInput提供默认值。

在父组件中


const ReportParamsScreen = props => {
    const report        = useSelector(state => state.report);
    const dispatch = useDispatch();

    useEffect(() => { 
        dispatch(actions.setReportParams({ 
            start_date  : moment().subtract(30, "days"),
            end_date    : moment()
        }))
    }, [dispatch]);

    const getStartDate = () => {
      if(report && report.params && report.params.start_date) {
         return report.params.start_date
      }
      return moment()
    }
    return (
        <View style={styles.row}>
            <View style={styles.column}>
                <DateInput 
                    mode="date"
                    timestamp={getStartDate()}
                />
            </View>
            ...
        </View>
    )
};

export default ReportParamsScreen;