const AUTH_DISABLED = 0;
const AUTH_LOGIN = 1;
const AUTH_LOGOUT = 2;
function App() {
const [showLoginPopup, setShowLoginPopup] = useState(false);
const [authButtonState, setAuthButtonState] = useState(AUTH_DISABLED);
const handleClick = () => {
if ( authButtonState == AUTH_LOGOUT ) {
authGlobal.logout().then( () => { setAuthButtonState(AUTH_LOGIN) } );
} else {
setShowLoginPopup(true)
}
};
if (authButtonState === AUTH_DISABLED) {
authGlobal.tryAuthentication().then((loggedIn) => {
setAuthButtonState(loggedIn ? AUTH_LOGOUT : AUTH_LOGIN);
});
}
return (
<div className="main-container">
<div> Display </div>
<div className='main-controls'>
<button type='button' onClick={handleClick} disabled = {authButtonState == AUTH_DISABLED}>Log {authButtonState == AUTH_LOGOUT ? "Out" : "In"} </button>
<LoginPopup isVisible={showLoginPopup}/>
</div>
</div>
)
}
In code above tryAuthentication
is async and has 3 paths: credentials are in local storage, credentials are in URL (just logged in with external app) or there is nothing to log in with.
My problem is that, in second case, button doesn’t change to "Log Out". State is never updated but then()
calls setAuthButtonState()
correctly.
Putting that branch in useEffect()
helps but I don’t know why.
Shouldn’t then()
queue another rerender with new state without UseEffect()
?
3
Answers
I’ve refactor your component for better handling of the asynchronous authentication flow.
So, in summary, while it might seem like the then() should queue another re-render with the new state without useEffect, in practice, the asynchronous nature of your tryAuthentication function and the way React batches state updates can lead to the issue you observed. Using useEffect provides a more controlled and predictable way to handle side effects and asynchronous operations in functional components.
In React, state updates are asynchronous, and the component may not re-render immediately after calling
setAuthButtonState
. Thethen
callback in yourtryAuthentication
function is likely resolving outside of the React rendering cycle, which means React doesn’t immediately recognize the state change.Here’s a modified version of your code
You are triggering a side effect on render. Side effects on render need to be done inside a
useEffect
hook: https://dev.to/hellonehha/what-is-side-effect-in-reactjs-and-how-to-handle-it-39j8See the official documentation about this too.