React-router-dom in Jira Cloud plugin

Hi!
I stuck with routing in my atlassian connect app.
I have SPA with pages:
<plugin.balse.url>/items - all items are displayed in table with links to every single item
<plugin.balse.url>/items/(id-of-item) - page with single item

when I run this app locally everything is working, but when I deploy my app to my cloud instance, my base url path is changed(because my app is displayed in iframe) to
https://(jira-site).atlassian.net/plugins/servlet/ac/my-plugin/items

All items in the table are shown correct, but when I clicked on the separate item, I recieve 404 error

I think the problem with displaying my app in iframe and (in this case) react-router wouldn’t work here.
I’m not sure about that, so If anyone uses routing in cloud app, please help me

1 Like

We have tried with Hash tag approach in our app and it worked for us.

https://{domain}.atlassian.net/plugins/servlet/ac/{key}/app-entry?project.key=DEMO&project.id=10000#!/Manage/{newpage}

constructor(props) {
    super(props);
    if (window.AP) {
      window.AP.getLocation(location => {
        
        // Below condition is mandatory for reload application on same page
        if (location) {
          const locParts = location.split("#!");
          if (locParts.length > 1) {
            this.props.history.push(locParts[1]);
          }
        }
      });
      this.props.history.listen(location => {
        let loc = location.pathname;
        if (window.AP) {
          if (location.search) {
            loc += location.search;
          }
          const jiraState = window.AP.history.getState();
          if (loc !== jiraState) {
            window.AP.history.pushState(loc);
          }
        }
      });
      // This will listen instance hash change event and open the page accordingly
      window.AP.history.popState(e => {
        if (e && e.newURL) {
          this.props.history.push(e.newURL);
        }
      });
    }
  }
1 Like

Thank you! You helped me a lot!

I appreciate the response but could you be more elaborate here? I assume this is a class constructor that extends some component but which component did you extend?

It’s not so complicated actually. Simply use HashRouter as default router from the router-dom package:

import {
HashRouter as Router,
Route,
Switch,
} from ‘react-router-dom’;

You can use react-router normally then

2 Likes

Does that preserve your location after a page reload? For instance, if I navigate to /some/path in my iframe and then reload the site, will I end up on the same page in my iframe?

for that to work, you need to sync the hash changes with AP.history as outlined in the solution by @umang.savaliya

Nice, will have a look at that. Thanks.

I have tweaked @umang.savaliya’s answer to work with React Router v6 and Typescript. The following is all I needed to persist the location across page reloads.

import React, {PropsWithChildren, useEffect} from 'react';
import {useNavigate, useLocation} from 'react-router-dom';

declare global {
    interface Window {
        AP: any;
    }
}

interface RouterSync {
}

function RouterSync({children}: PropsWithChildren<RouterSync>) {
    const navigate = useNavigate();
    const location = useLocation();

    useEffect(() => {
        if (window.AP) {
            window.AP.getLocation((location : any) => {
                if (location) {
                    const locParts = location.split("#!");
                    if (locParts.length > 1) {
                        navigate(locParts[1]);
                    }
                }
            });
        }
    }, []);

    useEffect(() => {
        let loc = location.pathname;
        if (window.AP) {
            if (location.search) {
                loc += location.search;
            }
            const jiraState = window.AP.history.getState();
            if (loc !== jiraState) {
                window.AP.history.pushState(loc);
            }
        }
    }, [location])

    return <>{children}</>;
}

export default RouterSync;

You would then use it as some descendant of HashRouter like so:

root.render(
    <AppContainer>
            <HashRouter>
                <RouterSync>
                    <PageLayout>
                      ...
2 Likes