【问题标题】:Why React not re-rendering components even if it's state changes after an API call为什么即使在 API 调用后状态发生变化,React 也不重新渲染组件
【发布时间】:2021-09-14 21:08:36
【问题描述】:

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

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

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

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

加载组件后,我调用 API 并在 API 承诺解决后填充数组

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

    setNextOccurance(pattern.NextOccurance);       
}

这是我的使用效果

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

现在问题来了

  • callender 组件只渲染一次,其 props 中包含空数组
  • 即使在我更改状态后,React 也不会重新渲染
  • 当我将 'upcomingDates' 放入 useEffect(...,[upcomingDates]) 第二个参数时,React 陷入无限循环

这是来自控制台的 API 响应。记录“模式”变量

如何让日历组件重新渲染?为什么即使我改变了状态,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>
    ); }

【问题讨论】:

标签: javascript reactjs render use-effect


【解决方案1】:

以下是正在发生的一切以及为什么它不起作用:

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

当状态改变时组件肯定会重新渲染,并且效果正如你所期望的那样,具有空依赖数组的 useEffect 仅在挂载和卸载时调用。

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

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

【讨论】:

  • 谢谢伙计。我了解无限循环的原因。我担心的是为什么即使在我调用“setUpcommingDates()”之后组件也不重新渲染它的子组件。因为我的其他部分随着状态的变化而适当变化
  • 这里有一些可以尝试的方法。一,在你的useEffect中去掉关键字async,你无论如何都不能将异步回调传递给useEffect,你也不会等待任何东西。在那之后,你能做类似useEffect(() =&gt; console.log(upcomingDates), [upcomingDates]) 的事情并告诉我一些结果吗?
  • ' 无论如何,你什么都没有等待' - 我正在等待 useEffect() 中的 API 调用。我应该将调用移至其他函数吗?
  • 在你的例子中你没有在等待它,你只是在调用异步函数。
【解决方案2】:

我发现了问题。

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

我必须将它放在子组件(日历组件)中,以便在日期更改时重新呈现

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>
    );
}

【讨论】:

    猜你喜欢
    • 2020-02-18
    • 2020-11-05
    • 1970-01-01
    • 1970-01-01
    • 2022-11-04
    • 1970-01-01
    • 2020-07-07
    • 1970-01-01
    • 2017-01-23
    相关资源
    最近更新 更多