Google Identity - sign In with google button not visible when go to other screen and comeback - google-signin

I am trying to integrate the Google SSO using the Google Identity API's for the Angular 14 application.
The problem I am facing is, I can see the Sign In with Google button when I first come into Login screen. But if I go to other screen then do logout and when I am back to Login screen, the Sign In with google button is no more visible and I have to force refresh (Ctrl+Shift+R) to make it visible.
I have already gone through Why does the Sign In With Google button disappear after I render it the second time?
but it is unclear how to make feasible in my case.
As I can see an Iframe will be rendered during the 1st time and if I come back again to login page from other page I can not see the Iframe and the SignIn button is not visible.
Here is the code to load the sign in button from angular component
ngOnInit() {
// #ts-ignore
window.onGoogleLibraryLoad = () => {
// #ts-ignore
window.google.accounts.id.disableAutoSelect();
};
this.loadGoogleSSOScript('2.apps.googleusercontent.com');
}
loadGoogleSSOScript(clientId: string) {
// #ts-ignore
window.onGoogleLibraryLoad = () => {
// #ts-ignore
google.accounts.id.initialize({
client_id: clientId,
itp_support: true,
callback: this.handleCallback.bind(this),
});
// #ts-ignore
google.accounts.id.renderButton(
// #ts-ignore
document.getElementById('g_id_onload'),
{ theme: 'filled_blue', size: 'medium', width: '200px' }
);
// #ts-ignore
google.accounts.id.prompt(); // also display the dialog
};
}
Here is the link for Stackblitz which has full code.
How to solve this issue?

The way I solved this problem was to move the button to another place in the dom rather than allow it to be destroyed when my component is destroyed.
When I need the button again, I move it again. I use "display:none" when I'm storing the button in another location in the dom.
Here's an example of moving the button:
// to be called in onDestroy method of component that renders google button
storeButton() {
const con = this.document.getElementById(this.storageConID);
const btn = this.document.getElementById(this.googleButtonID);
con!.insertBefore(btn as any, con?.firstChild || null);
}
However, I found that trying to move the button between locations too quickly, with multiple consecutive calls to el.insertBefore would actually cause the button to disappear from the dom altogether for some reason.
In my case, I was navigating between a login and a signup page and both needed to display the button. To get around that issue, I used a MutationObserver and made sure that I didn't try to move the button out of it's "storage" location until it was actually there.
I added this to my index.html as a place to store the button when it shouldn't be displayed.
<div id="google-btn-storage-con">
<div id="google-btn" class="flex-row justify-center items-center hidden"></div>
</div>
The div with the id "google-btn" is the element I pass into the google.accounts.id.renderButton method to render the button initially on the page.
When I need to display the google button, then I move the div with the id "google-btn" into my component.
I hope that this little bit of code and explanation is enough. I would share more but the actual implementation of all this is hundreds of lines long (including using MutationObserver and dynamically loading the gsi script).

In Angular you shouldn't directly access the DOM to get the element, you can use ViewChild.
//HTML
<div #gbutton></div>
//TS
export class LoginComponent implements OnInit, AfterViewInit {
#ViewChild('gbutton') gbutton: ElementRef = new ElementRef({});
constructor() { }
ngAfterViewInit() {
google.accounts.id.initialize({
client_id: clientId,
itp_support: true,
callback: this.handleCallback.bind(this),
});
google.accounts.id.renderButton(
this.gbutton.nativeElement,
{
type: "standard", theme: "outline",
size: "medium", width: "50", shape: "pill", ux_mode: "popup",
}
)
}

Related

nextjs reload page with Link component

I have a simple nextjs 13 application (appDir) with 2 pages and a <Link/> component navigation.
first page Home - static
second page Test - receiving dynamic random content on the server side (fetch) from a third-party source.
Problem: When the application is rendered everything works fine, but when I try to switch between pages, my test page shows the old content, I can refresh the browser to get the actual data, is very similar to navigating through regular links <a/>, but i need without reloading the entire application.
Q: How can I force nextjs 13 to reload the Test page when I switch between pages with <Link/> component?
// src/components/navbar.tsx
'use client'
import {usePathname} from "next/navigation";
import Link from "next/link";
const navItems = [
{text: 'Home', href: '/'},
{text: 'Test', href: '/test'}
];
const Navbar = () => {
const pathname = usePathname();
return <nav className="nav nav-masthead justify-content-center float-md-end">
{navItems.map((item: { text: string, href: string, link?: boolean }, idx: number) => (
<Link key={idx} href={item.href} className={`nav-link${item.href === pathname ? ' active' : ''}`}>
{item.text}
</Link>
)
)}
</nav>
}
export default Navbar;
// src/app/test/page.tsx
import * as crypto from "crypto";
const getData = async () => {
const res = await fetch('http://localhost:3000/random-data', {cache: 'no-store'});
if (!res.ok) {
throw new Error('Failed to fetch data');
}
return res.json();
}
export default async function Page() {
return <p>{crypto.createHash('sha256').update(JSON.stringify(await getData())).digest('hex')}</p>
};
I've recently asked about this same topic on their feedback discussion on github: https://github.com/vercel/next.js/discussions/41745?sort=new#discussioncomment-4620262
The cause of the problem is Link is only doing client side navigation and seems to serve a cached state of the previously visited component. You'll notice that the client never calls back to the server and thus the server component never runs the second time.
I've been searching for days, but haven't found a way to force Link to reload or force refresh the component to re-render.
My conclusion is that if you have dynamic data that needs to refreshed periodically, it's best to render it in a client component and not use a server component for now.
Also, if you'd like to use Suspense, you'll need to use a library like SWR or React Query for any client side data fetching.

CSS crashes when I reload page

I am building a site with Gatsby.
I am using a component that imports a script and returns a form.
The problem is, that after you loaded the page that shows the form, and then you click to any other page and go back to that form page, the css fully crashes for the entire site and you have to refresh the whole page.
To check out what I mean click this link https://baerenherz.org/, go to the dark blue button on the very right of the menu, then click to any other navigation site and then click again on the blue button (jetzt-spenden).
Here is my component for the donation form :
import React, { useState, useEffect } from "react"
import {Helmet} from "react-helmet"
import Loading from "./Loading"
function Child() {
return(
<div style={{width: "75%", margin: "4em auto"}} >
<Helmet>
<script type='text/javascript' aysnc>
{` window.rnw.tamaro.runWidget('.dds-widget-container', {language: 'de'}) `}
</script>
</Helmet>
<div className="dds-widget-container"></div>
</div>
)
}
function RaiseNow() {
const [loaded, setLoaded] = useState(false)
useEffect(() => {
const scriptTag = document.createElement('script')
scriptTag.src='https://tamaro.raisenow.com/xxx/latest/widget.js'
scriptTag.addEventListener('load', ()=> setLoaded(true))
document.body.appendChild(scriptTag)
return ()=>{
scriptTag.removeEventListener(); // check if necessary
setLoaded(false) // check if necessary
}
}, []);
return (
<>
{loaded ? <Child /> : <Loading/>}
</>
)
}
export default RaiseNow
What I noticed is, that the second time you visit the page, the Loading.... component does not even show anymore.. the Layout is displayed but as soon as the form shows, it crashes...
Since I cannot solve this issue since literally last year I would really appreciate any help with this. Thank you in advance.
Apparently, your script is breaking React's hydration when the component should be mounted/unmounted. There's no "clean" solution if there's no React-based script available. The problem here is that your script is manipulating the DOM while React manages the virtual DOM (vDOM). Changes in the DOM outside React's scope are not listened to by React and vice versa.
That said, I'd try forcing the loading and rendering of your widget each time the page loads. Something like:
function RaiseNow() {
const [loaded, setLoaded] = useState(false)
useEffect(() => {
const scriptTag = document.createElement('script')
scriptTag.src='https://tamaro.raisenow.com/xxx/latest/widget.js'
scriptTag.addEventListener('load', ()=> setLoaded(true))
document.body.appendChild(scriptTag)
window.rnw.tamaro.runWidget('.dds-widget-container', {language: 'de'})
return ()=>{
scriptTag.removeEventListener('load', setLoaded(false)); // check if necessary
setLoaded(false) // check if necessary
}
}, []);
return (
<>
{loaded ? <Child /> : <Loading/>}
</>
)
}
export default RaiseNow
Without a CodeSandbox it's difficult to guess how the code will behave but what it's important is to detach and clean up the listeners when the component is removed from the UI to avoid breaking React's hydration process, in the return statement. From the useEffect docs:
The clean-up function runs before the component is removed from the UI
to prevent memory leaks. Additionally, if a component renders multiple
times (as they typically do), the previous effect is cleaned up before
executing the next effect. In our example, this means a new
subscription is created on every update. To avoid firing an effect on
every update, refer to the next section.
There, besides removing the listeners from the script, you can also set the loading state to false.
I've also removed the second useEffect because the idea to avoid the CSS breaking is to force the loading of the script in each page rendering. It's not an optimal solution but it may work for you. The ideal solution would be using React-based dependencies.
Another thing to take into account is to delay the trigger of your rnw.tamaro script until the DOM tree is loaded, by moving it from the Helmet to the useEffect. This should ensure that your div and the window are available.
Turns out it was a issue on their end. Since they did an update it works.

Google identity service Signin Button don't appear without page refresh

I got a strange behavior from Google Identity Service signin button while implementing it with react. When I first visit the signin page the Google signin button do not appear but one tap window appear. If I refresh the page then both appears. After that if I navigate to other page and come back to signin page button disappear again but one tap window appear.
page first loading
page after browser refresh
I used the following code for signin button
renderGoogleSignInButton = () => {
return (
<>
<div
id="g_id_onload"
data-client_id="MY_CLIENT_ID"
data-auto_prompt="false"
data-auto_select="true"
data-callback="handleCredentialResponse"
></div>
<div
className="g_id_signin mt-4 flex justify-center"
data-type="standard"
data-size="large"
data-theme="outline"
data-text="sign_in_with"
data-shape="rectangular"
data-logo_alignment="left"
></div>
</>
)
}
and following code for one tap window
componentDidMount() {
google.accounts.id.initialize({
client_id: MY_CLIENT_ID,
callback: this.handleCredentialResponse,
})
google.accounts.id.prompt()
}
I didn't find any clue using google search, not even in the docs.
Thanks in advance for your help.
For those who will have this problem in future with react.
constructor(props) {
super(props)
window.handleCredentialResponse = this.handleCredentialResponse
this.myRef = React.createRef()
}
componentDidMount() {
if (window.google) {
window.google.accounts.id.initialize({
client_id:MY_CLIENT_ID
callback: this.handleCredentialResponse,
})
window.google.accounts.id.prompt()
window.google.accounts.id.renderButton(this.myRef.current, {
theme: 'outline',
size: 'large',
})
}
}
......
}
It sounds like the One Tap prompt is being displayed as expected, but it was a little unclear. If you're having issues with One Tap, Check out the PromptMomentNotification and getNotDisplayedReason values, they should help you to understand the reason why the One Tap prompt may not be displayed. You might also consider One Tap in an iframe if there's an issue with React being hard to debug.
For the button, if you've not found anything suspicious with pre-rendering, caching or when the div tag containing g_id_signin is loaded you might try rendering the button browser side using JS and renderButton.
I had the same issue - login button would not appear without a refresh. For me, it was simply a matter of moving the below logic from App.tsx to Login.tsx.
useEffect(() => {
dispatch(setCurrentPage("login"));
google.accounts.id.initialize({
client_id: process.env.REACT_APP_GOOGLE_CLIENT_ID!,
callback: handleCallbackResponse
});
// eslint-disable-next-line #typescript-eslint/no-non-null-assertion
const googleLoginDiv: HTMLElement = document.getElementById("googleLoginDiv")!;
google.accounts.id.renderButton(googleLoginDiv, {
type: "standard",
theme: "outline",
size: "large"
});
}, []);

React: Stop click event propagation when using mixed React and DOM events

We have a menu. If menu is open, We should be able to close it by clicking anywhere:
class Menu extends Component {
componentWillMount() {
document.addEventListener("click", this.handleClickOutside);
}
componentWillUnmount() {
document.removeEventListener("click", this.handleClickOutside);
}
openModal = () => {
this.props.showModal();
};
handleClickOutside = ({ target }) => {
const { displayMenu, toggleMenu, displayModal } = this.props;
if (displayMenu) {
if (displayModal || this.node.contains(target)) {
return;
}
toggleMenu();
}
};
render() {
return (
<section ref={node => (this.node = node)}>
<p>
<button onClick={this.openModal}>open modal</button>
</p>
<p>
<button onClick={this.openModal}>open modal</button>
</p>
<p>
<button onClick={this.openModal}>open modal</button>
</p>
</section>
);
}
}
From menu, we can open a modal by clicking on button inside menu. We can close modal in two ways: by clicking close modal button inside modal, or on click on bakcdrop/overlay outside the modal:
class Modal extends Component {
hideModal = () => {
this.props.hideModal();
};
onOverlayClick = ({ target, currentTarget }) => {
if (target === currentTarget) {
this.hideModal();
}
};
render() {
return (
<div className="modal-container" onClick={this.onOverlayClick}>
<div className="modal">
<button onClick={this.hideModal}>close modal</button>
</div>
</div>
);
}
}
And now, when menu and modal is open, on close modal click or modal overlay click I want to close only modal, menu should be still open. Only on second click (while modal is closed). At first glance it look pretty clear and easy, this condition should be responsible for that:
if (displayModal || this.node.contains(target)) {
return;
}
If displayModal is true, nothing should happen. I'ts do not work, cause in my case, when you click at the close modal button or overlay, hideModal will be done faster than toggleMenu, and when we call handleClickOutside displayModal will already have false.
Full test case with open menu and modal at the start:
https://codesandbox.io/s/reactredux-rkso6
This is gonna be a bit longer, as I have investigated in similar issue recently. If you don't want to read everything, just have a look at the solutions.
Solutions
Two solutions come to my mind - the first is the easy fix, the second is cleaner, but requires an additional click handler component.
1.) Easy fix
In Modal.js onOverlayClick, add stopImmediatePropagation like this:
onOverlayClick = e => {
// this is to stop click propagation in the react event system
e.stopPropagation();
// this is to stop click propagation to the native document click
// listener in Menu.js
e.nativeEvent.stopImmediatePropagation();
if (e.target === e.currentTarget) {
this.hideModal();
}
};
On document, there are two click listener registered: a) the first is the top level listener of React b) your click listener in Menu.js. With e.nativeEvent you get the native DOM event wrapped by React. stopImmediatePropagation will cancel the second listener - and prevents closing of the menu, when you just want to close the modal. You can read more under explanation.
Codesandbox
2.) The clean one
With this solution, you can just use event.stopPropagation. All event handling (incl. the outside click handler) is done by React, so you don't have to use document.addEventListener("click",...) anymore. The click-handler.js down under will be just some proxy that catches all click events at the top level and forwards them in the React event system to your registered components.
Create click-handler.jsx:
import React from "react";
export const clickListenerApi = { addClickListener, removeClickListener };
export const ClickHandler = ({ children }) => {
return (
<div
// span click handler over the whole viewport to catch all clicks
style={{ minHeight: "100vh" }}
onClick={e => {
clickListeners.forEach(cb => cb(e));
}}
>
{children}
</div>
);
};
// state of registered click listeners
let clickListeners = [];
function addClickListener(cb) {
clickListeners.push(cb);
}
function removeClickListener(cb) {
clickListeners = clickListeners.filter(l => l !== cb);
}
Menu.js:
class Menu extends Component {
componentDidMount() {
clickListenerApi.addClickListener(this.handleClickOutside);
}
componentWillUnmount() {
clickListenerApi.removeClickListener(this.handleClickOutside);
}
openModal = e => {
// This click shall not close the menu,
// so don't propagate the event to our clickListener API.
e.stopPropagation();
const { showModal } = this.props;
showModal();
};
render() {... }
}
index.js:
const App = () => (
<Provider store={store}>
<ClickHandler>
<Page />
</ClickHandler>
</Provider>
);
Codesandbox
Explanation:
When you have both modal dialog and menu open and click once outside the modal, then with your current code the behavior is correct - both elements are closed. That is because in the DOM document has already received the click event and prepares itself to invoke your handleClickOutside click handler in Menu. So you have no chance anymore to cancel it via e.stopPropagation() in onOverlayClick callback of Modal.
In order to understand the order of both firing click events, we have to comprehend that React has its own synthetic Event Handling system (1, 2). The main point here is that React uses top level event delegation and adds one single listener to document in the DOM for all event types.
Let's say you have a button <button id="foo" onClick={...}>Click it</button> somewhere in the DOM. When you click the button, it triggers a regular click event in the browser, that bubbles up to document and further until it reaches DOM root. React catches this click event with its single listener at document, and then internally traverses its virtual DOM again (similar to capture and bubble phase of the native DOM) and collects all relevant click callbacks that you have set with onClick={...} in your components. So your button onClick will be found and invoked later on.
Here is the interesting part: by the time React deals with the click events (which are now synthetic React events), the native click event had already gone through the full capture/bubbling cycle in the DOM and doesn't exist in the native DOM anymore! That is the reason, why a mix of native click handlers (document.addEventListener) and React onEvent attributes in the components' JSX sometimes is so hard to handle and unpredictable. React event handlers should always be preferred.
Links to read on:
Understanding React's Synthetic Event System (also the linked article with it)
ReactJS SyntheticEvent stopPropagation() only works with React events?
https://fortes.com/2018/react-and-dom-events/
Hope, it helps.
Call event.stopPropagation() inside your onOverlayClick() method and hideModal(), this will prevent the event to bubble up to the parent. Like this.
onOverlayClick = e => {
e.stopPropagation();
if (e.target === e.currentTarget) {
this.hideModal();
}
};
Just add a little timeout to let the displayMenu variable updates:
class Modal extends Component {
hideModal = () => {
const { hideModal } = this.props;
setTimeout(() => {
hideModal();
}, 200); // Could work with 100ms
};
...
Working example

Hiding Submit Button with offline.js

I am using offline.js (0.7.18) and its working fine, when I go on/offline the indicator changes state, all good so far.
I can also see on Offline JS Simulate UI, that a login panel is having its style toggled between display:block and display:none when on/offline is triggered. But I can't discover how this works.
I want to hide a 'Submit' button when offline is triggered.
You can use the sample from the Offline JS Simulate UI page that uses jQuery:
<script>
$(function(){
var
$online = $('.online'),
$offline = $('.offline');
Offline.on('confirmed-down', function () {
$online.fadeOut(function () {
$offline.fadeIn();
});
});
Offline.on('confirmed-up', function () {
$offline.fadeOut(function () {
$online.fadeIn();
});
});
});
</script>
Give the class "online" to any element you want shown when the system is online and give the class "offline" to any element you want shown when the system is offline.
<button class="online">ABC</button>

Resources