我正在使用
我有一个 React 功能组件,它依赖于存储在上下文中的数据来正确呈现,其中一些数据在显示之前需要额外处理,而一些额外数据需要获取。该组件正在抛出 React 检测到 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();
}
})
}
}
我只是想了解为什么会出现此错误以及任何潜在的解决方案或我做错了什么等。下面是完整的错误。如果我注释掉 setThisLoading(false)
的 .then()
中的 Promise.all()
,错误会消失,但我的页面永远不会显示任何内容,因为我使用 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()
钩子由外部库通知栈提供
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
}
我也尝试按照建议链接 promise 并添加一个虚拟清理函数,但我仍然遇到相同的错误。
useEffect(() => {
setThisLoading(true);
if(organisation && addresses && !loading) {
getMainContact()
.then(() => {
getMainAddress()
.then(() => {
getBillingContact()
.then(() => {
getBillingAddress()
.then(() => {
setThisLoading(false);
})
})
})
})
}
return () => {};
}, [organisation, addresses, loading])
答案 0 :(得分:1)
我发现问题与错误指示的组件完全不同。 setThisLoading(false)
是一个红鲱鱼,因为这只是允许问题组件呈现,因此给出错误。我是通过 Chrome 的控制台发现这个问题的,我通常在 Firefox 中工作,因为这是我选择的浏览器,但这次 Chrome 来救援,因为它提供了有关错误来源的更多信息。
我正在构建的应用程序具有用户角色的概念,允许/拒绝用户执行某些任务。我编写了一些函数来帮助禁用按钮和/或根据登录用户的角色不显示内容。这就是问题所在。
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 />
);
}
};
import { roleCheck } from '../Utils/RoleCheck';
...
<Button variant="outlined" disabled={roleCheck("organisation", 2)} color="primary">
edit organisation
</Button>
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
}
}
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()
钩子,因此不会违反规则钩子!