Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

App Bridge 4 clicking on Navigation Menu causes app reload #240

Closed
kirillplatonov opened this issue Oct 1, 2023 · 33 comments
Closed

App Bridge 4 clicking on Navigation Menu causes app reload #240

kirillplatonov opened this issue Oct 1, 2023 · 33 comments
Labels
bug Something isn't working

Comments

@kirillplatonov
Copy link

Describe the bug

When clicking on any link in Navigation Menu the app is fully reloaded to open new page. It starts loading from scratch with splash screen, token fetching, etc. This slows down loading dramatically and just looks weird for users.

To Reproduce

Steps to reproduce the behaviour:

  1. Define Navigation Menu with at least two pages:
<ui-nav-menu>
  <a href="/first-page">First page</a>
  <a href="/second-page">Second page</a>
</ui-nav-menu>
  1. Launch app
  2. Click on "First page" link
  3. Click on "Second page" link
  4. Notice full app reloading
CleanShot.2023-10-01.at.20.25.12.mp4

Expected behaviour

App Bridge should treat Navigation Menu clicks as in-app navigation. Same as navigating relative link within app: https://shopify.dev/docs/api/app-bridge-library/reference/navigation#example-navigating-to-routes-within-your-app

If this is not possible then at least we should have a way to add event handler and override Navigation Menu behaviour. This is how we've done it with App Bridge 3: https://github.com/kirillplatonov/shopify-hotwire-sample/blob/main/app/javascript/shopify_app/navigation.js#L25

Contextual information

Packages and versions

  • App Bridge 4 from CDN

Platform

  • App: Chrome, Safari, Firefox

Additional context

Tech stack:

  • Rails
  • Hotwire
  • Polaris ViewComponents
@kirillplatonov kirillplatonov added the bug Something isn't working label Oct 1, 2023
@Rafi993
Copy link

Rafi993 commented Oct 2, 2023

I can see this issue too.

@patryk-smc
Copy link

patryk-smc commented Oct 2, 2023

I have the same issue. There is currently no way to subscribe to Redirect action. This is last thing that prevents me from migrating to the CDN version

@MitchLillie
Copy link

Hello, thanks for your report.

Can you try specifying your app's root path as the first item in the nav menu, and adding rel="home" to that link? This is what our docs specify.

For example:

<ui-nav-menu>
  <a href="/" rel="home">Home</a>
  <a href="/first-page">First page</a>
  <a href="/second-page">Second page</a>
</ui-nav-menu>

@Rafi993
Copy link

Rafi993 commented Oct 3, 2023

Hello, thanks for your report.

Can you try specifying your app's root path as the first item in the nav menu, and adding rel="home" to that link? This is what our docs specify.

For example:

<ui-nav-menu>
  <a href="/" rel="home">Home</a>
  <a href="/first-page">First page</a>
  <a href="/second-page">Second page</a>
</ui-nav-menu>

That doesn't seem to work. I think the page has to be cached like it was done in the previous app bridge

@asacarter
Copy link

Use react Link to="" instead of <a href=""

@bithaolee
Copy link

We are experiencing the same problem, please Shopify considers our users who don't use Remix, I looked at the original app bridge react part of the code and there is a CustomRouter defined in there to synchronize the paths between shopify and the app, how can we implement this please

@Rafi993
Copy link

Rafi993 commented Oct 12, 2023

Use react Link to="" instead of <a href=""

Thanks. This actually works!!

@bithaolee
Copy link

Use react Link to="" instead of <a href=""

Thank you very much. It really works.
image

@kirillplatonov
Copy link
Author

This doesn't solve the problem for non-react apps. We still need a proper way to make it work with normal links without hacks. Same way as Navigation component works: https://shopify.dev/docs/api/app-bridge-library/reference/navigation#example-navigating-to-routes-within-your-app

@paweltatarczuk
Copy link

paweltatarczuk commented Oct 13, 2023

You can do this in plain JavaScript. The key is to prevent browser's default behaviour with event.preventDefault(). Something like this should work:

<ui-nav-menu>
  <a href="/" rel="home" onclick="navigate">Home</a>
  <a href="/first-page" onclick="navigate">First page</a>
  <a href="/second-page" onclick="navigate">Second page</a>
</ui-nav-menu>

<script>
function navigate(event) {
  event.preventDefault();
  history.pushState({}, null, event.target.href);
}
</script>

@kirillplatonov
Copy link
Author

Yeah, it's true. We doing something similar for Turbo as well. But it's still broken out of the box and needs to be fixed.

@awd
Copy link

awd commented Oct 14, 2023

You can do this in plain JavaScript. The key is to prevent browser's default behaviour with event.preventDefault(). Something like this should work:

<ui-nav-menu>
  <a href="/" rel="home" onclick="navigate">Home</a>
  <a href="/first-page" onclick="navigate">First page</a>
  <a href="/second-page" onclick="navigate">Second page</a>
</ui-nav-menu>

<script>
function navigate(event) {
  event.preventDefault();
  history.pushState({}, null, event.target.href);
}
</script>

Is this actually working for you? I've just tested it out and there isn't any difference. The AppBridge navigation is still sending unauthenticated requests into the app - causing page reloading.

It's definitely a hack and behaviour could easily break if not part of AppBridge by default - so I would not advise caution to anyone reading into this.

@kirillplatonov has the right sentiment here - AppBridge should send these requests with the Bearer token provided, so the app can simply load the view without reloading/redirecting.

@paweltatarczuk
Copy link

Yes, it works for me and no, it's not a hack. This is what React Router Link is doing under the hood.

@kirillplatonov
Copy link
Author

This code only updates browser URL without navigating to specific page:

history.pushState({}, null, event.target.href);

CleanShot 2023-10-15 at 08 23 58@2x

It's suitable as a fix for #242 when you need to keep focus on the right menu element. But not as a fix for navigation itself.

@paweltatarczuk
Copy link

I incorrectly assumed that the example app is an SPA. In this case dynamic links won't help as they would require some kind of router that would update the page accordingly.

Maybe it would be possible to workaround this issue with View Transitions API but it's just a guess. I hope app bridge gets a fix for static links in the Navigation component.

@iamphonghg
Copy link

For those using React, @shopify/app-bridge 3.4.3, react-router-dom 5.3.0.
I also encountered the app reloading when clicking on a navigation item until I used this code on the AppBridgeProvider:
app.subscribe(Redirect.Action.APP, function() {});

@henrytao-me
Copy link
Member

Hi everyone, I have a few notes about this:

With the new App Bridge via Shopify CDN

  • There is no subscribe to App Redirect like the older version of App Bridge.
  • If you just define UINavMenu like below, it will redirect your app like any other multiple pages app.
<ui-nav-menu>
  <a href="/first-page">First page</a>
  <a href="/second-page">Second page</a>
</ui-nav-menu>
  • To stop the redirect which makes your app as single page app, you need to handle click event and call preventDefault() like below which is what the Link component does in remix template
<ui-nav-menu>
  <a href="/first-page">First page</a>
  <a href="/second-page">Second page</a>
</ui-nav-menu>

// In Javascript
document.querySelector('ui-nav-menu').addEventListener('click', (e) => {
  if (e.target.nodeName === 'A') {
     // handle your own redirect here
      e.preventDefault();
  }
});

With all older version of App Bridge

  • Subscribing to App Redirect is the way to make your app as a single page app.

@awd
Copy link

awd commented Dec 11, 2023

Hi @henrytao-me

I've tried adding the click event listener, and have confirmed the ui-nav-menu is found in the DOM, but the click event is not firing. I suspect the version on this CDN is not the latest?

  • What version is being served through: https://cdn.shopify.com/shopifycloud/app-bridge.js ?
  • How do we check for the latest version? (the global shopify object does not appear to have this info)
  • Is there a way to reference a specific version on the Shopify CDN?

@PhiloNL
Copy link

PhiloNL commented Dec 24, 2023

Hi @henrytao-me

I'm having trouble with the event listener as well. It only seems to trigger when clicking back and forth.

Screen.Recording.2023-12-24.at.22.25.44.mov

Update
Managed to get it working by adding history.pushState({}, null, event.target.href); after handling the redirect.

@daviareias
Copy link

Use react Link to="" instead of <a href=""

Where is this component front? from polaris doesn't have a to field anymore.

@henrytao-me
Copy link
Member

Hi all, I just want to follow up on this issue. Let's me know if anyone is still having issue with this.

TL;DR about how App Bridge CDN handles redirect

@sebastianpisula
Copy link

It was helpful for me

Use react Link to="" instead of <a href=""

Thank you very much. It really works. image

@jbeladiya
Copy link

jbeladiya commented Apr 8, 2024

Hi all, I just want to follow up on this issue. Let's me know if anyone is still having issue with this.

TL;DR about how App Bridge CDN handles redirect

Hi @henrytao-me,

For single-page apps, manually implementing redirection may work, but afterward, if you click the browser's back button, it might not redirect properly. This issue could occur because the URL is not signed with HMAC. Please let me know if there is a solution for this problem.

@tobiasdalhof
Copy link

tobiasdalhof commented Apr 8, 2024

Vue 3 example:

<script setup lang="ts">
import { useRouter } from 'vue-router'
import { ref, watch } from 'vue'

const router = useRouter()

function navigate(event: MouseEvent) {
  const pathname = (event.target as HTMLAnchorElement).pathname
  history.pushState({}, '', pathname)
  router.replace(pathname)
}

// Re-render ui-nav-menu on route change
const key = ref(0)
watch(router.currentRoute, () => key.value++)
</script>

<template>
  <ui-nav-menu :key="key">
    <a href="/" rel="home" @click.prevent="navigate">Home</a>
    <a href="/page-1" @click.prevent="navigate">Page 1</a>
    <a href="/page-2" @click.prevent="navigate">Page 2</a>
  </ui-nav-menu>
</template>

@jbeladiya
Copy link

jbeladiya commented Apr 8, 2024

Vue 3 example:

<script setup lang="ts">
import { useRouter } from 'vue-router'
import { ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'

const router = useRouter()
const { t } = useI18n()

function navigate(event: MouseEvent) {
  const pathname = (event.target as HTMLAnchorElement).pathname
  history.pushState({}, '', pathname)
  router.replace(pathname)
}

// Re-render ui-nav-menu on route change
const key = ref(0)
watch(router.currentRoute, () => key.value++)
</script>

<template>
  <ui-nav-menu :key="key">
    <a href="/" rel="home" @click.prevent="navigate">{{ t('home') }}</a>
    <a href="/page-1" @click.prevent="navigate">{{ t('page-1') }}</a>
    <a href="/page-2" @click.prevent="navigate">{{ t('page-2') }}</a>
  </ui-nav-menu>
</template>

Hi @tobiasdalhof,
The solution provided doesn't work for me. Additionally, I have a React app, and my code is as follows:

import { useNavigate } from "react-router-dom"; // v6

const navigate = useNavigate();

const pageRedirect = useCallback((e) => {
    e.preventDefault();
    window.history.pushState({}, '', e.target.href);
    navigate(e.target.pathname);
}, [navigate]);
  
<NavMenu>
        {Object.values(shopifyNavigation).map((i, index) => {
          return (
            <a
              href={i.destination}
              key={index}
              onClick={pageRedirect}
            >
              {i.label}
            </a>
          );
        })}
</NavMenu> 

Here, I'm loading all nav-items dynamically

@sebastianpisula
Copy link

@jbeladiya , example from my project:

import {NavMenu} from "@shopify/app-bridge-react";
import {Link} from "react-router-dom";

<NavMenu>
            <Link to="/">General settings</Link>
            <Link to="/faq">FAQ</Link>
            <Link to="/support">Support</Link>
            <Link to="/plans-and-pricing">Plans and pricing</Link>
        </NavMenu>

@jbeladiya
Copy link

jbeladiya commented Apr 8, 2024

General settings

@sebastianpisula, approach is same. When I click on a navigation item, I am redirected to a specific route with that particular item highlighted. However, I am facing an issue because in my app, I have a back button to redirect to the previous screen or a specific route. When I click on the back button, it navigates to the specific route, but the corresponding navigation item is not highlighted at that time.

demo.mp4

@umutnacak
Copy link

Hi @jbeladiya If you didn't already solve your issue here is a code snippet for you to try:

import { NavMenu } from "@shopify/app-bridge-react";
import { useMemo } from "react";
import { useLocation, Link } from "react-router-dom";

const location = useLocation();
const navMenu = useMemo(() =>
<NavMenu key={location.key}>
  {Object.values(shopifyNavigation).map((i, index) => {
    return (
      <Link to={i.destination}>
        {i.label}
      </Link>
    );
  })}
</NavMenu>, [location, shopifyNavigation]);

{ navMenu }

I didn't test with dynamic values so am not sure if it works like this, but here is a simple snippet that I already use in production:

import { NavMenu } from "@shopify/app-bridge-react";
import { useMemo } from "react";
import { useLocation, Link } from "react-router-dom";

const location = useLocation();
const navMenu = useMemo(() =>
<NavMenu key={location.key}>
  <Link to="/" rel="home">
    Home
  </Link>
  <Link to="/page1">
    Page1
  </Link>
  <Link to="/page2">
    Page2
  </Link>
</NavMenu>, [location]);

{ navMenu }

Hope this helps future readers.

@jbeladiya
Copy link

Hi @jbeladiya If you didn't already solve your issue here is a code snippet for you to try:

import { NavMenu } from "@shopify/app-bridge-react";
import { useMemo } from "react";
import { useLocation, Link } from "react-router-dom";

const location = useLocation();
const navMenu = useMemo(() =>
<NavMenu key={location.key}>
  {Object.values(shopifyNavigation).map((i, index) => {
    return (
      <Link to={i.destination}>
        {i.label}
      </Link>
    );
  })}
</NavMenu>, [location, shopifyNavigation]);

{ navMenu }

I didn't test with dynamic values so am not sure if it works like this, but here is a simple snippet that I already use in production:

import { NavMenu } from "@shopify/app-bridge-react";
import { useMemo } from "react";
import { useLocation, Link } from "react-router-dom";

const location = useLocation();
const navMenu = useMemo(() =>
<NavMenu key={location.key}>
  <Link to="/" rel="home">
    Home
  </Link>
  <Link to="/page1">
    Page1
  </Link>
  <Link to="/page2">
    Page2
  </Link>
</NavMenu>, [location]);

{ navMenu }

Hope this helps future readers.

Hi @underwoodsimon it's work for me. Thanks.
But other side browser back not working properly when I click on browser back two or three time then after it's redirect to previous route. If you have also solution for it let me know.

@umutnacak
Copy link

@jbeladiya I also faced that issue during development, most likely because react renders twice in strict mode. So probably there are multiple entries of the same location in the browser history and you need to click twice or more on the back button. You need to test on production to see if there is a difference.

@liliangrong777
Copy link

Use react Link to="" instead of <a href=""

👍

@bithaolee
Copy link

This code only updates browser URL without navigating to specific page:

history.pushState({}, null, event.target.href);

CleanShot 2023-10-15 at 08 23 58@2x

It's suitable as a fix for #242 when you need to keep focus on the right menu element. But not as a fix for navigation itself.

I'm trying to switch pages via navigate and change the active menu in the left menus, but I can't seem to get it to work, I'm using open('/path', '_self') but it causes the app to be reloaded, there's Any solution?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests