【问题标题】:Why does my useEffect run multiple times even when that component should only be rendered once?为什么我的 useEffect 运行多次,即使该组件应该只呈现一次?
【发布时间】:2022-08-06 23:14:04
【问题描述】:

即使 useEffect 依赖项未更新,我的 useEffect 每次加载也会运行多次。请参阅 console.log(\"过滤事件\").每次加载页面时,useEffect 都会运行 3 次(我最好的猜测是因为组件被渲染了 3 次?)。

我想要做的是每次更新 filters 状态变量时,我希望从应用于所有事件的过滤器中更新 filteredEvents 变量,然后使用新事件重新渲染日历组件.

import React, { useState, useEffect, useCallback } from \'react\'
import { Calendar as BigCalendar, momentLocalizer } from \'react-big-calendar\';
import moment from \'moment\';
import loadingIMG from \"../../../assets/images/courses/signup/minionsLoading.gif\"
import \"react-big-calendar/lib/css/react-big-calendar.css\"
import {
    Accordion,
    AccordionItem,
    AccordionItemHeading,
    AccordionItemPanel,
    AccordionItemButton
} from \'react-accessible-accordion\'
import { navigate, Link } from \'gatsby\';

moment.locale(\'en-US\')
const localizer = momentLocalizer(moment)

const CourseSignup = ({className}) => {
    const [loading, isLoading] = useState(true)

    const [allEvents, setAllEvents] = useState({
        count: undefined,
        events: undefined
    })

    useEffect(async () => {
        console.log(\"fetching events\")
        await fetch(\"http://127.0.0.1:5000/timeslots\", { 
            method: \'GET\',
            headers: {
                \'X-API-Key\': \'*F-JaNdRgUkXp2s5v8x/A?D(G+KbPeSh\',
            }
        })
        .then(response => response.json())
        .then((results) => {
            let temp = []
            // Get all events
            for (var i = 0; i < results.events.length; i++) {
                // TODO: Check if class is virtual or in-person
                // TODO: Check if class is group or 1 on 1
                // TODO: get teacher name
                // TODO: get class name

                // Get all neccesary information and push to an array
                temp.push({
                    title: String(results.events[i].summary),
                    info: {
                        format: undefined,
                        type: undefined,
                        name: undefined,
                        teacher: undefined
                    },
                    description: String(results.events[i].description) || undefined,
                    start: new Date(results.events[i].start.date || results.events[i].start.dateTime),
                    end: new Date(results.events[i].end.date || results.events[i].end.dateTime),
                    url: String(results.events[i].htmlLink),
                    id: String(results.events[i].id),
                    est_locale_timecodes: {
                        start: results.events[i].start.date || results.events[i].start.dateTime,
                        end: results.events[i].end.date || results.events[i].end.dateTime
                    }
                })
            }
            // update state variable with events
            setAllEvents({
                \'count\': temp.length,
                \'events\': temp
            })
        })
        .catch(() => {
            // incase of an error replace all events
            setAllEvents({
                \'count\': undefined,
                \'events\': undefined
            })
        })
        .then(() => isLoading(false))
    }, [])


    const UserInterface = () => {
        const [filters, updateFilters] = useState({
            classTeacher: \'undefined\', // name of the teacher (default: undefined)
            classType: undefined, // \'group\', \'1 on 1\'
            classFormat: undefined, // \'virtual\', \'in-person\'
            class: className // name of class (default: undefined)
        })
        const [filteredEvents, setFilteredEvents] = useState({
            count: undefined,
            events: undefined
        })

        useEffect(() => {
            console.log(\"filtering events\")
            if(!Object.values(filters).every(filterValue => filterValue == undefined)) {
                // let filtered = Array(allEvents.events).filter(event => {
                //     if(event.info.format == undefined || String(event.info.format).includes(filters.classFormat == undefined ? \"\" : filters.classFormat)) return true
                //     if(event.info.type == undefined || String(event.info.type).includes(filters.classType == undefined ? \"\" : filters.classType)) return true
                //     if(event.info.name == undefined || String(event.info.name).includes(filters.class == undefined ? \"\" : filters.class)) return true
                //     if(event.info.teacher == undefined || String(event.info.teacher).includes(filters.classTeacher == undefined ? \"\" : filters.classTeacher)) return true
                //     return false
                // })

                setFilteredEvents(allEvents)
            } else {
                setFilteredEvents(allEvents)
            }
        }, [filters])

        const FilterOptions = () => {
            return null
        }

        const Calendar = () => {
            const getDate = () => {
                var d = new Date()
                return (`${d.toLocaleString(\'default\', { month: \'long\' })}, ${d.getFullYear()}`)
            }

            return (
                <div id=\"calendar-content\">
                    <div className=\"section-title\" style={{\"marginBottom\":\"10px\"}}>
                        <h3 style={{fontFamily: \"inherit\"}}>{getDate()}</h3>
                    </div>
                    <BigCalendar 
                        localizer={localizer}
                        events={filteredEvents.events} 
                        startAccessor=\"start\" 
                        endAccessor=\"end\" 
                        style={{ height: 800 }} 
                        views={{month: true}} 
                        popup={true}
                        toolbar={false}
                    />
                </div>
            )
        }

        const SignupForm = () => {
            return null
        }

        return (
            <div>
                <FilterOptions/>
                <div>
                    {loading
                        ? null
                        : <Calendar/>
                    }
                </div>
                <SignupForm/>
            </div>
        )
    }

    return (
        <UserInterface/>
    )

}

export default CourseSignup

    标签: reactjs use-effect use-state


    【解决方案1】:

    如果是,您是否在 index.js 中使用 StrictMode 组件包装您的 App 组件?然后将其删除,您将获得预期的结果,并且该组件仅渲染一次

    你做错的另一件事不被认为是一个好的做法是,不要像这样将回调函数声明为异步 =>

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

    因为如果你这样做,那么如果你想在下次运行时从 useEffect 内部返回一个清理函数,你就不会这样做

    例如,假设您有一个通过 useEffect 与后端绑定的搜索引擎,现在在每次输入更改时都会触发 useEffect

    useEffect(() => {} , [onEveryInputChange])
    

    现在每次 useEffect 运行时都会注册一个超时,一旦时间到期,就会向后端发送一个请求

    useEffect(() => {
        const myTimeout = setTimeout(() => fetch('someUrl').then().catch(), [2000])
    }, [onEveryInputChange])
    

    现在您不想为每个输入发送 req,而您只想在完成输入后发送 req,或者在写下几个单词后暂停 2 秒,所以在这种情况下,我们使用清理功能

    useEffect(() => {
          const myTimeout = setTimeout(() => fetch('someUrl').then().catch(), [2000])
        
        return () => clearTimeout(myTimeout) // This is a cleanup function
    }, [onEveryInputChange])
    

    因此,下次您在 2 秒内输入内容时,清理将首先运行 并且会阻止req被执行

    【讨论】:

    • 我没有用 StrictMode 包装任何东西,我会考虑从异步 useEffect 更改
    • 你检查了吗,因为当我们使用 npx create-react-app 创建一个应用程序时,它会自动用严格模式组件包装应用程序组件
    • 是的,我的顶层只是一个 RecoilRoot
    • 这是 React 18 的“功能”。另见levelup.gitconnected.com/…
    • 我正在使用反应 17
    猜你喜欢
    • 2017-06-19
    • 2022-01-05
    • 2021-05-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-26
    • 2020-07-31
    • 1970-01-01
    相关资源
    最近更新 更多