amplify-ui: useAuthenticator doesn't trigger re-render when using at the app level
Before creating a new issue, please confirm:
- I have searched for duplicate or closed issues and discussions.
- I have read the guide for submitting bug reports.
On which framework/platform are you having an issue?
React
Which UI component?
Authenticator
How is your app built?
Create React App
Please describe your bug.
I am trying to use the useAuthenticator hook to set a global authentication state.
However, several problems have arisen:
- When I use
const { user, signOut } = useAuthenticator((context) => [context.user]);, as is mentioned here, there is no app-level re-rendering. So several other components that are also using the hook and are dependent on authentication state don’t change. - If I refresh the page, React renders everything as signed out. When I go to sign in, it then signs me in automatically without me having to manually sign in. It should ideally render everything as signed in from the start, since I am already signed in.
What’s the expected behaviour?
When I sign in or out, React should trigger an app-level re-render with the new authentication state. Furthermore, if I refresh the page, the authentication state should not change.
Help us reproduce the bug!
Following the instructions, I added Authenticator.Provider at the app level:
ReactDOM.render(
<Authenticator.Provider>
<HelmetProvider>
<BrowserRouter>
<App />
</BrowserRouter>
</HelmetProvider>
</Authenticator.Provider>,
document.getElementById('root')
);
I use useAuthenticator to Login:
const { route } = useAuthenticator(context => [context.route]);
return (
route === 'authenticated' ? <Navigate to="/dashboard/app" />: (
<Authenticator
// Default to Sign Up screen
initialState="signUp"
// Customize `Authenticator.SignUp.FormFields`
signUpAttributes={['preferred_username', 'birthdate']}
components={components}
services={{
async validateCustomSignUp(formData) {
if (!formData.acknowledgement) {
return {
acknowledgement: 'You must agree to the Terms & Conditions',
};
}
},
}}
/>
)
);
I define const { user, signOut } = useAuthenticator((context) => [context.user]); in components, not at the app level, where I want to obtain the authentication state. Following the guide, I use the conditional typeof user === 'undefined' to see if the user is authenticated.
Code Snippet
// Put your code below this line.
Additional information and screenshots
No response
About this issue
- Original URL
- State: open
- Created 2 years ago
- Reactions: 5
- Comments: 53 (22 by maintainers)
It baffles my mind that this has not been fixed. The reason we are trying to use
Authentication.ProvideranduseAuthenticatorin our application is because if we create a custom login/sign-up form without it, it appears there is no way to automatically sign-in the user after they confirm their email without using these. Even using them, it’s unclear whether this is possible.https://github.com/aws-amplify/amplify-js/issues/2562
If I use my own custom components and call
Authmethods manually, I have to have the user login again after confirmation (ruins user experience and onboarding) or temporarily store the login credentials in the browser as mentioned in the issue above.I don’t understand why there is even documentation for a headless usage of
Authenticatorif it doesn’t respond toAuth.signInevents and only responds toAuth.signOutevents. It led me down a rabbit hole of developer pain and in my opinion, this lack of functionality makes it completely useless.In addition, there appears to be no way to trigger a transition to
verifyUserso that I can use thesendForminternal method in the workaround discussed above.It also appears nonsensical to me how there can be all of these different authStates but there are only transitions available for just a few of them. If this is the case, how am I meant to manually transition to any of the others?
This is not an advanced use case. This is a use case that tons of developers will have. Assuming developers will just lightly modify the slots or CSS variables for the Authenticator is crazy. Obviously developers will want to build their own custom authentication UI components. In my case, I just have a custom form and want to use that to perform authentication instead of the pre-built UI.
It’s quite unbelievable that this has not yet been fixed after 1 year! Come on guys, lots of devs rely on this stuff.
It does seem to be working for me now @reesscot! Still having some issues, but I think they may be on my end, elsewhere in the code. In console logs, I’m seeing updated values for
userandauthStateas expected fromuseAuthenticatorafter signIn. Previously, I wasn’t seeing updated values. Thanks!One thing I didn’t explicitly check is
autoSignIn, which I think utilizes a differentHubevent. We don’t need it for our current use case, but would be good to ensure that it supported as well.Either way, thanks for digging in. Much appreciated!
@kyokosdream It is now possible to Automatically sign in using the JS Auth API’s. See https://docs.amplify.aws/lib/auth/emailpassword/q/platform/js/#auto-sign-in-after-sign-up
We are working on supporting your use cases above, and will update this ticket when we have more detailed plan.
Ok, I can confirm that using only
<Authenticator.Provider>withaws-amplify/authmethods works pretty well withuseAuthenticatorfor a fully custom UI experience. Here’s the simple implementation that works with no force window reload to re-render etc. for login or logout needed anduseAuthenticatorhook works beautifullyReact+Vite
App.jsx
main.jsx
AppLayout.jsx (has SignOut button)
Actually quite shocked to see how well this works.
I had much better luck with the Authenticator component and provider when I was using
amplify-jsversion 5. On version 6, not as much luck. I found a workaround that may help others.I only use
authStatusfromuseAuthenticator. This seems to update to “authenticated” upon login using the Authenticator component, but I could not get it to update to “unauthenticated” upon logout.I found a workaround for that issue.
I’m also using redux, and at signOut, I do not use the
signOutmethod fromuseAuthenticator. Instead, I use:import { signOut } from "aws-amplify/auth";And my sign-out button handler then does this:
setLogoutIntis a redux action, anddispatchis the redux Dispatch.Then, this was the trick that got things working for me: forcing a rerender of the Provider upon logout via redux:
With this workaround, I am able to use amplify v6 with the Authenticator component and logout successfully. Hope it helps! Good luck.
@calebpollman
Generally, the documentation is good, but since time has changed the capabilities it seems like it is a bit dated.
I’m using React Native but this approach also works with React or JavaScript.
Firstly, the documentation relies on examples. There isn’t the classic documentation style that outlines each property, class or method that is available on each hook or module. This would already be helpful and probably easier to maintain. When something is deprecated the page that the information is displayed can also say it has been deprecated. This is relatively common when reviewing documentation for other APIs.
Secondly, currently the AWS Amplify customization docs suggest this approach but this is not quite right because the NBM "module itself seems to handle the revealing of the children (see my earlier post’s “Other finds” section).
Lastly, if you search for “handlesubmit” on the authenticator documentation page the word doesn’t exist
It would at the bare minimum be helpful to know that this exists as a prop when instantiating a custom “Sign In” view.
For an example, you can go with the a basic approach, which satisfies almost all use-cases.
Use case: I want to add custom authentication to my app. Result:
Provider
MySignIn component
Note the use of
handleSubmitversus what the documentation suggests with issignIn()You can see the documentation suggests using
signIn()here. and in several other places for JavaScript or React. This is misleading as we both know that this doesn’t work as expected.I would suggest including information at this point in the documentation that explains the use of
handleSubmit, even if this is a temporary alternative and a fix is coming in the future. Because for people like myself I almost had to forego using AWS Amplify due to the lack of ability to log a user in with a custom form. That is probably not ideal for AWS Amplify.I hope this helps and wish you luck improving the docs. Thanks!
@adriaanbalt Glad that you got things working!
The documentation could be improved here, think the addition of a concrete example for this use case would be a good starting point but curious if there is anything else that you would have found helpful?
@adriaanbalt That was a mistake on my end. It’s available as a prop passed in to the
SignIncomponent:Hey @adriaanbalt. Are you calling
signIndirectly from insideMySignIn?@reesscot I’ll add to this – one of the ways in which
useAuthenticatorseems to be not working as I would expect is in regards to “headless” usage ofAuthenticator.Provider, with a custom UI (in my case, viaAuth.signUp).This earlier answer from @ErikCH was really helpful – this seems to be the exact issue I’m running into: (emphasis mine)
Can you please confirm that this is indeed still an issue? If so, it seems the “roll your own provider” approach is the best way forward for now? Is there any plan to continue to add support for the additional hub events propagating to
useAuthenticator?EDIT: to clarify – I’m not doing any page refreshes, it is a single-page app – so the issue is purely that after successfully executing
Auth.signUp(either with auto-login, or without auto-login followed by an explicit call toAuth.signIn),useAuthenticatoris not aware of the updated, logged-in state.I ran into this as well, especially if I refreshed the application. I ended up putting together a quick user provider of my own:
Then I use it where ever I need user info like a top menu or anything else:
That works for both reloading and auth changes. I have a separate Login page where I just use the Authenticator and my own context to check if I should redirect back:
Hope this helps someone in the meantime!
Hi @silberistgold !
If you’re using
Auth.signIn, that will not propagate touseAuthenticatoror the auth state. You are correct.The only thing we listen for is
Auth.signOut. That will propagate. We are looking at adding morehub eventsto listen to, so you can interact directly with the JS library, outside of theAuthenticator.The
submitFormis an internal event name, and it’s not mentioned in our documentation. It’s used for both sending thesignUpandsignIninformation. Just beware, you need to be on the correct route, for it to work. You can use thetoSignUportoSignInto get to that.With all that said, I wouldn’t recommend using it in the long term, since it could change. However, we are looking into adding better documentation and more utilities to make creating your own headless
Authenticatora better experience for advanced used cases like yours.Hi @vymao !
Yes, so we made a change with #1580 that improves the experiences for users that are on multiple routes. So now as long as you have your application surrounded by
Authenticator.ProvidertheuseAuthenticatorwill work on any route you’re on. Before, if you didn’t have anAuthenticatoron your page it wouldn’t work.There is still one more outstanding issue with this solution. On refresh the
routewill temporarily be in asetuporidlestate before it transitions to anauthenticatedstate. If you check route as soon as the page loads, and redirect somewhere while it’s in theidleorsetupstate, that could be an issue.I created a guide on authenticated routes here. In it I describe this scenario, and work around for it.
In this scenario if someone goes to an authenticated route, and it’s an
idleorsetupstate then we redirect back to/login. Which will then by that time see the user isauthenticatedand will re-route back to theauthenticatedpage.Let me know if that’s what you’re experiencing and if this work around helps in the mean time.