为什么 React 不重新渲染组件,即使它的状态在 API 调用后发生变化

Why React not re-rendering components even if it's state changes after an API call

当组件初始化时,我有一个像这样用空数组初始化的 React 状态

const [upcomingDates, setUpcomingDates] = useState([]);

而这个日历组件依赖于这个状态。 (这是我们主要组件的一个 chld 组件,并且具有这种状态依赖性)

<Calender name="Eat Eggs" dates={upcomingDates} />

组件加载后,我调用 API 并在 API promise resolves

后填充数组
async function refreshPattern()
{
    //Call the API
    let pattern = await GetRepeatPattern(id);
    //Update State
    setUpcomingDates(pattern.UpcomingDates);

    setNextOccurance(pattern.NextOccurance);       
}

这是我的useEffect

useEffect(async () => {
 refreshPattern();
},[]);

现在的问题是

这是来自控制台的 API 响应。记录 'pattern' 变量

如何让日历组件重新渲染?为什么即使我更改了状态,React 也不会重新渲染组件?

注意:这是完整的组件。

export const RepeatSelector = ({ id }) => {

    let dropdownPatternMode = null;


    const [every, setEvery] = useState(1);
    const [timeMode, setTimeMode] = useState(1);
    const [course, setCourse] = useState(1);
    const [constrain, setConstrain] = useState(1);
    const [patternMode, setPatternMode] = useState(1);
    const [nextOccurance, setNextOccurance] = useState('Loading...');
    const [upComingDates, setUpcomingDates] = useState([]);
    const [dayOfMonth, setDayOfMonth] = useState(1);
    const [logicalStart, setLogicalStart] = useState(1);
    const [logicalDay, setLogicalDay] = useState(1);
    const [pattern, setPattern] = useState({
        Value: '',
        Mode: '',
        Constrain: '',
        Time: '',
        Date: ''
    });
    const [startDay, setStartDay] = useState(new Date());
    const [startTime, setStartTime] = useState('00:00');
    const [days, setDays] = useState([
        {
            selected: false,
            name: "SUN"
        },
        {
            selected: false,
            name: "MON"
        },
        {
            selected: true,
            name: "TUE"
        },
        {
            selected: true,
            name: "WED"
        },
        {
            selected: false,
            name: "THU"
        },
        {
            selected: false,
            name: "FRI"
        },
        {
            selected: false,
            name: "SAT"
        },
    ]);


    function init() {
        dropdownPatternMode = new Choices(document.getElementById('everyCount'), { searchEnabled: false, shouldSort: false });
    }


    useEffect(async function () {
        init();
        let response = await GetActivityRepeat(id);
        setDayOfMonth(response.DayOfMonth);
        
        days.map((day, index) => {
            if (day.name == 'SUN') {
                days[index] = { selected: response.Sunday, name: 'SUN' };
            }
            else if (day.name == 'MON') {
                days[index] = { selected: response.Monday, name: 'MON' };
            }
            else if (day.name == 'TUE') {
                days[index] = { selected: response.Tuesday, name: 'TUE' };
            }
            else if (day.name == 'WED') {
                days[index] = { selected: response.Wednesday, name: 'WED' };
            }
            else if (day.name == 'THU') {
                days[index] = { selected: response.Thursday, name: 'THU' };
            }
            else if (day.name == 'FRI') {
                days[index] = { selected: response.Friday, name: 'FRI' };
            }
            else if (day.name == 'SAT') {
                days[index] = { selected: response.Saturday, name: 'SAT' };
            }
            return days;
        });

        setDays(days);

        setLogicalStart(response.LogicalStart);
        setLogicalDay(response.LogicalDay);
        setCourse(response.Course);
        setEvery(response.PatternValue);

        setPatternMode(response.PatternMode);
        dropdownPatternMode.setChoiceByValue([response.PatternMode.toString()]);
        setStartDay(new Date(response.StartBy));
        setConstrain(response.Constrain);
        let myTime = response.SpecificTime.split('T')[1];
        setStartTime(myTime);
        

        async function refreshPattern()
        {
            //Call the API
            let pattern = await GetRepeatPattern(id);
            //Update State
            setUpcomingDates(pattern.UpcomingDates);
            setPattern(pattern);
            setNextOccurance(pattern.NextOccurance);       
        }
        refreshPattern();

        setTimeMode(response.TimeMode);
    }, []);

    async function SaveData() {
        let sDate = new Date().toISOString().substring(0, 10) + " 00:00";
        let sTime = new Date().toISOString().substring(0, 10) + " 00:00";

        try {
            sDate = startDay.toISOString().substring(0, 10) + " 00:00";
        }
        catch
        {
            store.addNotification({
                title: "Start Date Required",
                message: "Please choose a start date for this activity",
                type: "warning",
                insert: "top",
                container: "top-right",
                animationIn: ["animate__animated", "animate__fadeIn"],
                animationOut: ["animate__animated", "animate__fadeOut"],
                dismiss: {
                    duration: 2000,
                    pauseOnHover: true
                }
            });
            return;
        }

        try {
            sTime = new Date().toISOString().substring(0, 10) + " " + startTime;
        }
        catch
        {
            if (timeMode == 2) {
                store.addNotification({
                    title: "Start Time Required",
                    message: "Please choose a start time for this activity",
                    type: "warning",
                    insert: "top",
                    container: "top-right",
                    animationIn: ["animate__animated", "animate__fadeIn"],
                    animationOut: ["animate__animated", "animate__fadeOut"],
                    dismiss: {
                        duration: 2000,
                        pauseOnHover: true
                    }
                });
                return;
            }
        }

        var data = {
            ActivityId: parseInt(id),
            Course: course,
            PatternValue: every,
            PatternMode: patternMode,
            Sunday: days.find((e) => e.name == 'SUN').selected,
            Monday: days.find((e) => e.name == 'MON').selected,
            Tuesday: days.find((e) => e.name == 'TUE').selected,
            Wednesday: days.find((e) => e.name == 'WED').selected,
            Thursday: days.find((e) => e.name == 'THU').selected,
            Friday: days.find((e) => e.name == 'FRI').selected,
            Saturday: days.find((e) => e.name == 'SAT').selected,
            Constrain: constrain,
            DayOfMonth: dayOfMonth,
            LogicalStart: logicalStart,
            LogicalDay: logicalDay,
            StartBy: sDate,
            TimeMode: timeMode,
            SpecificTime: sTime,
        };
        await SaveActivityRepeat(data);
        store.addNotification({
            title: "Activity Saved",
            message: "Data saved successfully. Changes will reflect soon",
            type: "success",
            insert: "top",
            container: "top-right",
            animationIn: ["animate__animated", "animate__fadeIn"],
            animationOut: ["animate__animated", "animate__fadeOut"],
            dismiss: {
                duration: 2000,
                pauseOnHover: true
            }
        });
        //refreshPattern();
    }


    const renderRepeatPattern = () => {
        return (
            <>
                <ul className="list-group mt-3">
                    <li className="list-group-item border-0 d-flex p-4 mb-2 bg-gray-100 border-radius-lg">
                        <div className="d-flex flex-column">
                            <h6 className="mb-3 text-sm">Current Configuration</h6>
                            <span className="mb-2 text-xs">Pattern:
                                <span className="text-danger font-weight-bold ms-sm-2">{pattern.Value} {pattern.Mode} {pattern.Constrain} {pattern.Time} {pattern.Date}</span>
                            </span>
                            <span className="mb-2 text-xs">Next Occurance:
                                <span className="text-dark ms-sm-2 font-weight-bold">{nextOccurance}</span></span>
                        </div>
                        <div className="ms-auto text-end">
                            <a className="btn btn-link text-primary px-3 mb-0" href="javascript:;"><i className="fas fa-pencil-alt text-dark me-2" aria-hidden="true" />View Calender</a>
                        </div>
                    </li>
                </ul>
                <Calender name="Eat Eggs" dates={upComingDates} />
            </>
        );
    }

    const renderDaySelector = () => {
        if (patternMode == 1) {
            return (
                <>
                    {renderRepeatPattern()}
                    <hr className="horizontal dark mt-3" />
                    <label>Start From</label>
                    <div className="form-check">
                        <div className="row">
                            <div className="col-md-12">
                                <DatePicker value={startDay} onChange={setStartDay} />
                            </div>
                        </div>
                    </div>
                </>
            );
        }
        else if (patternMode == 2) {
            return (
                <>
                    <div className="col-md-12 mt-3">
                        <DaySelector data={days} onDataUpdate={(e) => setDays(e)} />
                    </div>
                    <>
                        {renderRepeatPattern()}
                        <hr className="horizontal dark mt-3" />
                        <label>Start From</label>
                        <div className="form-check">
                            <div className="row">
                                <div className="col-md-12">
                                    <DatePicker value={startDay} onChange={setStartDay} />
                                </div>
                            </div>
                        </div>
                    </>
                </>
            );
        }
        else if (patternMode == 3) {
            return (
                <div className="col-12 col-sm-12">
                    <label>Constrain By</label>
                    <div className="form-check">
                        <input checked={constrain === 1} className="form-check-input" type="radio" name="constrainBy" id="radDayOfMonth" onChange={() => setConstrain(1)} />
                        <label className="custom-control-label" htmlFor="radDayOfMonth">Day of Month</label>
                        <div className="row">
                            <div className="col-md-4">
                                <DayDropdown onSelection={(e) => setDayOfMonth(e)} value={dayOfMonth} />
                            </div>
                        </div>
                    </div>
                    <div className="form-check">
                        <input checked={constrain === 2} className="form-check-input" type="radio" name="constrainBy" id="radLogicaly" onChange={() => setConstrain(2)} />
                        <label className="custom-control-label" htmlFor="radLogicaly">Logicaly</label>
                        <div className="row">
                            <div className="col-md-4">
                                <WeekDropdown onSelection={(e) => setLogicalStart(e)} value={logicalStart} />
                            </div>
                            <div className="col-md-4">
                                <DayNameDropdown onSelection={(e) => setLogicalDay(e)} value={logicalDay} />
                            </div>
                        </div>
                    </div>
                    {renderRepeatPattern()}
                    <hr className="horizontal dark mt-3" />
                    <label>Start From</label>
                    <div className="form-check">
                        <div className="row">
                            <div className="col-md-12">
                                <DatePicker value={startDay} onChange={setStartDay} />
                            </div>
                        </div>
                    </div>
                </div>
            );
        }
        else if (patternMode == 4) {
            return (
                <>
                    {renderRepeatPattern()}
                    <hr className="horizontal dark mt-3" />
                    <label>Start From</label>
                    <div className="form-check">
                        <div className="row">
                            <div className="col-md-12">
                                <DatePicker value={startDay} onChange={setStartDay} />
                            </div>
                        </div>
                    </div>
                </>
            );
        }
    }



    return (
        <>
            <div className="row">
                {/*COURSE*/ }
                <div className="col-12 col-sm-12">
                    <label>Course</label>
                    <div className="col-12 col-sm-12">
                        <div className="form-check">
                            <input checked={course === 1} className="form-check-input" type="radio" name="course" id="radOneTime" onChange={() => setCourse(1)} />
                            <label className="custom-control-label" htmlFor="radOneTime">One Time</label>
                        </div>
                        <div className="form-check">
                            <input checked={course === 2} className="form-check-input" type="radio" name="course" id="radMultipleTimes" onChange={() => setCourse(2)} />
                            <label className="custom-control-label" htmlFor="radMultipleTimes">Multiple Times</label>
                        </div>
                    </div>
                </div>
                {/*PATTERN*/}
                <label className="custom-control-label mt-3" htmlFor="customRadio1">Running Pattern</label>
                <div className="col-md-1">
                    <label className="form-check-label" htmlFor="flexSwitchCheckDefault">Every</label>
                </div>
                <div className="col-md-2">
                    <input className="form-control" type="number" value={every} onChange={e => setEvery(Number(e.target.value))} />
                </div>
                <div className="col-md-4">
                    <select className="form-control" id="everyCount" onChange={e=>setPatternMode(Number(e.target.value))}>
                        <option value="1">Day</option>
                        <option value="2">Week</option>
                        <option value="3">Month</option>
                        <option value="4">Year</option>
                    </select>
                </div>

                {renderDaySelector()}

                {/*TIME*/}
                <label className="mt-3">Set Time</label>
                <div className="form-check">
                    <div className="row">
                        <div className="col-12 col-sm-12">
                            <div className="form-check">
                                <input checked={timeMode === 1} className="form-check-input" type="radio" name="time" id="radAdapt" onChange={() => setTimeMode(1)} />
                                <label className="custom-control-label" htmlFor="radAdapt">Adapt Automaticaly</label>
                            </div>
                            <div className="form-check">
                                <input checked={timeMode === 2} className="form-check-input" type="radio" name="time" id="radSpecificDate" onChange={() => setTimeMode(2)} />
                                <label className="custom-control-label" htmlFor="radSpecificDate">Specific Time</label>
                                {timeMode === 2 && <TimePicker value={startTime} onChange={setStartTime} />}
                            </div>
                        </div>
                    </div>
                </div>

            </div >

            <div className="card-footer pt-0 p-3 d-flex align-items-center">
                <div className="w-60"> <p className="text-sm"> </p>
                </div>
                <div className="w-40 text-end">
                    <a className="btn bg-gradient-primary mb-0 text-end" onClick={SaveData}>Save Details</a>
                </div>
            </div>
        </>
    );

}

这是日历组件

export const Calender = ({name, dates}) => {

    const [upcomingDates, setUpcomingDates] = useState([])

    useEffect(() =>
    {
        let dts = [];
        for (let i = 0; i < dates.length; i++) {
            dts.push(
                { title: name, date: dates[i], className: 'bg-gradient-dark' }
            );
        }
        setUpcomingDates(dts);
    }, []);

    return (
        <div className="card card-calendar">
            <div className="card-body p-3">
                <FullCalendar
                    allDayClassNames="calendar"
                    plugins={[dayGridPlugin]}
                    initialView="dayGridMonth"
                    weekends={false}
                    events={upcomingDates}
                />
            </div>
        </div>
    ); }

这是发生的所有事情以及它不起作用的原因:

第一,当您将状态依赖项添加到 useEffect 时,它会陷入无限循环,因为它调用的函数会修改该状态。

组件在状态改变时肯定会重新渲染,效果如您所料,具有空依赖数组的 useEffect 仅在挂载和卸载时调用。

如果它使用空数组重新渲染,很可能是因为 pattern.UpcomingDates 是一个空数组,而不是因为组件没有按预期运行。您是否尝试记录 pattern.UpcomingDates 是什么?

除此之外,其他人已经注意到您的组件中出现了一些反模式内容。您应该在效果内部声明该函数,或者使用 useCallback 创建它并将其添加到 useEffect.

的依赖数组中

我发现了问题。

而不是使用 "upcomingDates" 作为父组件 useEffect() 的依赖项

我不得不把它放在子组件(Calender 组件)中,这样它会在日期更改时重新呈现

export const Calender = ({name, dates}) => {

    const [upcomingDates, setUpcomingDates] = useState([])

    useEffect(() =>
    {
        let dts = [];
        for (let i = 0; i < dates.length; i++) {
            dts.push(
                { title: name, date: dates[i], className: 'bg-gradient-dark' }
            );
        }
        setUpcomingDates(dts);
    }, [dates]);

    return (
        <div className="card card-calendar">
            <div className="card-body p-3">
                <FullCalendar
                    allDayClassNames="calendar"
                    plugins={[dayGridPlugin]}
                    initialView="dayGridMonth"
                    weekends={false}
                    events={upcomingDates}
                />
            </div>
        </div>
    );
}