繁体   English   中英

React 检测到 Hooks 的调用顺序发生了变化,但我看不到任何有条件调用的 hooks

[英]React has detected a change in the order of Hooks called, but I can't see any hooks being called conditonally

我在用

  • ReactJS 16.14.0

我有一个 React 功能组件,它依赖于存储在上下文中的数据来正确呈现,其中一些数据在显示之前需要额外的处理,而一些额外的数据需要获取。 该组件正在抛出React 检测到 Hooks 错误的顺序发生变化,我已经阅读了有关 hooks 规则的 react 文档,并通过 SO 进行了很好的查看,但我无法弄清楚为什么会出现错误。 我已经缩短了下面的代码以保持简短。


    const { enqueueSnackbar } = useSnackbar();
    const [ mainContact, setMainContact ] = useState(undefined);
    const [ mainAddress, setMainAddress ] = useState(undefined);
    const [ thisLoading, setThisLoading ] = useState(true);
    const { organisation, addresses, loading } = useProfileState();


    useEffect(() => {
        setThisLoading(true);
        if(organisation && addresses && !loading) {
            Promise.all([getMainContact(), getMainAddress()])
            .then(() => {
                setThisLoading(false);
            })
            .catch(() => {
                console.log("Failed getting address/contact info");
                setThisLoading(false);
            })
        }
    }, [organisation, addresses, loading])


    const getMainContact = () => {
        return new Promise((resolve, reject) => {
            apiService.getData(`/organisation/users/${organisation.mainContact}`)
            .then(mainContact => {
                setMainContact(mainContact);
                return resolve();
            })
            .catch(error => {
                enqueueSnackbar(error, { variant: 'error' });
                return reject();
            })
        })
    }

    const getMainAddress = () => {
        return new Promise((resolve, reject) => {
            let mainAddress = addresses.find(addr => addr.id === organisation.mainAddress)
            if(mainAddress !== undefined) {
                setMainAddress(mainAddress);
                return resolve();
            } else {
                enqueueSnackbar("Error getting main address ", { variant: 'error' });
                return reject();
            }
        })
    }
}

我只是想了解为什么我会收到此错误以及任何潜在的解决方案或我做错了什么等。下面是完整的错误。 如果我在 Promise.all( .then()Promise.all() () 中setThisLoading(false) ,错误就会消失,但我的页面从不显示任何内容,因为我使用thisLoading有条件地呈现加载轮或内容。

   ------------------------------------------------------
1. useContext                 useContext
2. useDebugValue              useDebugValue
3. useContext                 useContext
4. useRef                     useRef
5. useRef                     useRef
6. useRef                     useRef
7. useMemo                    useMemo
8. useEffect                  useEffect
9. useEffect                  useEffect
10. useDebugValue             useDebugValue
11. useContext                useContext
12. useState                  useState
13. useState                  useState
14. useState                  useState
15. useState                  useState
16. useState                  useState
17. useState                  useState
18. useState                  useState
19. useContext                useContext
20. useEffect                 useEffect
21. undefined                 useContext
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

我只是想了解为什么setThisLoading(false)会导致我收到此错误。

更新

useSnackbar()挂钩由外部库 notistack https://github.com/iamhosseindhv/notistack提供

下面是与useProfileState()相关的代码

import React, { createContext, useContext, useReducer } from 'react';

const initialState = { loggedIn: false, loading: true, error: false }

const ProfileStateContext = createContext();
const ProfileDispatchContext = createContext();

const ProfileReducer = (state, action) => {
    switch (action.type) {
        case 'LOGGED_IN':
            console.log("PROFILE CONTEXT - Logged in");
            return { ...state, loggedIn: true }
        case 'LOADED':
            console.log("PROFILE CONTEXT - Data loaded");
            return { ...state, loading: false, error: false }
        case 'LOADING':
            console.log("PROFILE CONTEXT - Data loading");
            return { ...state, loading: true, error: false }
        case 'ERROR':
            console.log("PROFILE CONTEXT - Error");
            return { ...state, loading: false, error: true }
        case 'ADD_USER':
            console.log("PROFILE CONTEXT - Adding user...");
            return { ...state, user: { ...action.payload } }
        case 'ADD_ORGANISATION':
            console.log("PROFILE CONTEXT - Adding organisation...");
            return { ...state, organisation: { ...action.payload } }
        case 'ADD_ROLES':
            console.log("PROFILE CONTEXT - Adding roles...");
            return { ...state, roles: [...action.payload] }
        case 'ADD_ORGANISATIONS':
            console.log("PROFILE CONTEXT - Adding organisations...");
            return { ...state, organisations: [...action.payload] }
        case 'ADD_ADDRESSES':
            console.log("PROFILE CONTEXT - Adding addresses...");
            return { ...state, addresses: [...action.payload] }
        case 'LOGOUT':
            console.log("PROFILE CONTEXT - Removing context data...");
            return initialState;
        default:
            console.error(`Unhandled action dispatched to user reducer, action type was: ${action.type}`);
            return state;
    }
}

const ProfileProvider = ({ children }) => {

    const [state, dispatch] = useReducer(ProfileReducer, initialState)

    return (
        <ProfileStateContext.Provider value={state}>
            <ProfileDispatchContext.Provider value={dispatch}>
                {children}
            </ProfileDispatchContext.Provider>
        </ProfileStateContext.Provider>
    );
};

const useProfileState = () => {
    const context = useContext(ProfileStateContext);

    if (context === undefined) {
        throw new Error('useProfileState must be used within a ProfileContextProvider')
    }

    return context;
};

const useProfileDispatch = () => {
    const context = useContext(ProfileDispatchContext);

    if (context === undefined) {
        throw new Error('useProfileDispatch must be used within a ProfileContextProvider')
    }

    return context;
};

export {
    ProfileProvider,
    useProfileDispatch,
    useProfileState
}

更新 2

我也尝试过链接承诺并按照建议添加一个虚拟清理函数,但我仍然遇到同样的错误。

useEffect(() => {
    setThisLoading(true);
    if(organisation && addresses && !loading) {
        getMainContact()
            .then(() => {
                getMainAddress()
                    .then(() => {
                        getBillingContact()
                            .then(() => {
                                getBillingAddress()
                                    .then(() => {
                                        setThisLoading(false);
                                    })
                            })
                    })
            })
    }

    return () => {};
}, [organisation, addresses, loading])

我发现问题出在与错误指示的完全不同的组件中。 setThisLoading(false)是一个红鲱鱼,因为这只是允许问题组件呈现因此给出错误。 我发现这一点的方法是通过 Chrome 的控制台,我通常在 Firefox 中工作,因为这是我选择的浏览器,但这次 Chrome 提供了更多关于错误来源的信息。

我正在构建的应用程序具有用户角色的概念,允许/拒绝用户执行某些任务。 我编写了一些函数来帮助禁用按钮和/或根据登录用户的角色不显示内容。 这就是问题所在。

旧 RoleCheck.js

    import React from 'react';
    import { useProfileState } from '../../context/ProfileContext';

    //0 - No permission
    //1 - Read only
    //2 - Read/Write

    const _roleCheck = (realm, permission) => {

        const { organisation, organisations, roles, loading } = useProfileState();

        if(!loading) {

            //Get the RoleID for the current account
            const currentOrganisation = organisations.find(org => org.id === organisation.id);
            //Get the Role object by RoleID
            const currentRole = roles.find(role => role.id === currentOrganisation.roleId);

            if(currentRole[realm] === permission) {
                return true;
            } else {
                return false;
            }
        }

    };

    export const roleCheck = (realm, permission) => {

        //Reversed boolean for button disabling
        if(_roleCheck(realm, permission)) {
            return false;
        } else {
            return true;
        }

    };

    export const RoleCheck = ({ children, realm, permission }) => {
        
        if(_roleCheck(realm, permission)) {
            return (
                <React.Fragment>
                    { children }
                </React.Fragment>
            );
        } else {
            return (
                <React.Fragment />
            );
        }

    };

旧 RoleCheck.js 的使用

import { roleCheck } from '../Utils/RoleCheck';
...
<Button variant="outlined" disabled={roleCheck("organisation", 2)} color="primary">
    edit organisation
</Button>

新的 RoleCheck.js

import React from 'react';
import { useProfileState } from '../../context/ProfileContext';

//0 - No permission
//1 - Read only
//2 - Read/Write

export const useRoleCheck = () => {

    const { organisation, organisations, roles, loading } = useProfileState();

    const _roleCheck = (realm, permission) => {
    
        if(!loading) {
    
            //Get the RoleID for the current account
            const currentOrganisation = organisations.find(org => org.id === organisation.id);
            //Get the Role object by RoleID
            const currentRole = roles.find(role => role.id === currentOrganisation.roleId);
    
            if(currentRole[realm] === permission) {
                return true;
            } else {
                return false;
            }
        }
    
    };

    const RoleCheckWrapper = ({ children, realm, permission }) => {
    
        if(_roleCheck(realm, permission)) {
            return (
                <React.Fragment>
                    { children }
                </React.Fragment>
            );
        } else {
            return (
                <React.Fragment />
            );
        }
    
    };

    const roleCheck = (realm, permission) => {

        //Reversed boolean for button disabling
        if(_roleCheck(realm, permission)) {
            return false;
        } else {
            return true;
        }
    
    };

    return {
        roleCheck: roleCheck,
        RoleCheckWrapper: RoleCheckWrapper
    }

}

新 RoleCheck.js 的使用

import { useRoleCheck } from '../Utils/RoleCheck';
...
const RequiresRoleCheck = () => {

    const rc = useRoleCheck();

    return (
        <Button variant="outlined" disabled={rc.roleCheck("organisation", 2)} color="primary">
            edit organisation
        </Button>
    )

}

通过将我的角色检查函数变成一个钩子,我可以在其中调用钩子,并且我可以在需要使用它的组件的顶层调用useRoleCheck()钩子,因此不会违反钩子的规则!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM