I am using
- ReactJS 16.14.0
I have a React functional component that relies on data stored in context to render correctly, some of this data needs additional processing before display and some additional data needs fetching. the component is throwing the React has detected a change in the order of Hooks error, I have read the react docs on the rules of hooks as well as having a good look through SO but I can't work out why I get the error. I have shortened the code below to keep it brief.
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();
}
})
}
}
I just want to understand why I get this error and any potential solutions or what I am doing wrong etc. below is the full error. If I comment out the setThisLoading(false) in the .then() of the Promise.all() the error goes away but my page never displays any content because I use thisLoading to conditionally render a loading wheel or the content.
------------------------------------------------------
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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
I am just looking to understand why the setThisLoading(false) causes me to get this error.
Update
The useSnackbar() hook is provided by an external libary notistack
https://github.com/iamhosseindhv/notistack
Below is the code relating to 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
}
Update 2
I have also tried chaining the promises and adding a dummy cleanup func as suggested, I still get the same error.
useEffect(() => {
setThisLoading(true);
if(organisation && addresses && !loading) {
getMainContact()
.then(() => {
getMainAddress()
.then(() => {
getBillingContact()
.then(() => {
getBillingAddress()
.then(() => {
setThisLoading(false);
})
})
})
})
}
return () => {};
}, [organisation, addresses, loading])