Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

dynamic basename with BrowserRouter in react-router-dom

Please I have an issue building a multi-tenant SaaS solution. For every tenant, I want them to use a subdomain, so i can get the subdomain from the url, make a call to a REST api that returns data about that tenant.

For example,

  1. the admin (another app entirely - admin app) creates a tenant with domain name: tenant1.
  2. In the tenant application on my local system, I was able to go to tenant1.localhost:3000. I get the url, and get the domain name. I then make a call with the domain to get the theme of tenant (this is stored in localStorage).

Unfortunately, we deploy on k8 in my company and so I couldn't mimic this behavior. So i have been advised by the devOps team to use subdomain in the context, thereby having localhost:3000/tenant1. Remember the tenant is dynamic, so i tried this:

<BrowserRouter basename={"/:tenant"}>
    <Switch>
        <Route exact path="/login" name="Login" component={Login} />
        <Route exact path="/set-password/:token" name="Set Password" component={SetPassword} />
        <PrivateRoute path="/" name="Default Layout" component={DefaultLayout} /> 
    </Switch>              
</BrowserRouter>

The solution above however makes my url to localhost:3000/:tenant/login

Please how can i use dynamic basename in the router, so it can accept:

localhost:3000/tenant1 localhost:3000/tenant3 localhost:3000/tenant2 etc.

It can allow any, my app handles wrong domain inputted

like image 837
Lateefah Avatar asked Mar 05 '19 16:03

Lateefah


4 Answers

This worked for me using react >16 and react-router-dom v5

export const App = () => {
    return (
        <BrowserRouter>
            <Switch>
                <Route path="/:tenantId?" component={LayoutRoot} />
            </Switch>
        </BrowserRouter>
    );
};

export const LayoutRoot = () => {
    var { tenantId } = useParams();
     //TODO: add some validation here and inform user if tenant is invalid
    return (
        <BrowserRouter basename={tenantId}>
            <Switch>
                <Route path="/login" component={LoginComponent} />
                <Route path="/dashboard" component={DashboardComponent} />
            </Switch>
        </BrowserRouter>
    );
};
like image 90
dzed Avatar answered Nov 20 '22 10:11

dzed


Here's a codesandbox and the utility I wrote:

https://codesandbox.io/s/react-router-dom-dynamic-basename-xq9tj?file=/index.js

import urlJoin from 'url-join';

// it's important to have an identifier in their app
export const APP_ROOT_URL = '/my-app';

export const getBaseUrlPath = () => {
  const currentPath = document.location.pathname || APP_ROOT_URL;
  const startOfAppBase = currentPath.indexOf(APP_ROOT_URL);

  let base = currentPath;

  if (startOfAppBase !== -1) {
    base = currentPath.substr(0, startOfAppBase);
  }

  base = urlJoin(base, APP_ROOT_URL);

  return base;
};
like image 29
Cory Robinson Avatar answered Sep 27 '22 18:09

Cory Robinson


I finally used dynamic tenant with the following code

class App extends Component {

    state = {
        domain: ""
    }

    componentWillMount () {
        const { domain } = this.state;

        const parsedData = window.location.pathname.split("/"); 
        let domain = parsedData[1];
        this.setState({ domain: domain })
        this.props.onGetTenant(domain);                
    }

    render () {
        const { domain } = this.state;

        return () {
             <BrowserRouter basename={"/"+domain}>
                <Switch>
                    <Route exact path="/login" name="Login" component={Login} />
                    <Route exact path="/set-password/:token" name="Set Password" component={SetPassword} />
                    <PrivateRoute domain={domain} path="/" name="Default Layout" component={DefaultLayout} /> 
                 </Switch>              
             </BrowserRouter> 
    }

const mapStateToProps = state => {
    const { tenant} = state;
    return { tenant};
};

const mapDispatchToProps = (dispatch) => {
    return {
        onGetTenant: bindActionCreators( tenantActions.get, dispatch)
    }
};

export default connect(mapStateToProps, mapDispatchToProps)(App)
like image 12
Lateefah Avatar answered Nov 20 '22 08:11

Lateefah


You can render updates to your router's basename by using the key property. Any changes to the key value will cause the component to re-render.

Here's a code sandbox to demo: https://codesandbox.io/s/react-router-dom-dynamic-basename-forked-hnkk0?file=/index.js

You can hover or inspect the links in the sandbox to verify that their href values are correctly updating after changing the basename. You can also see that the hrefs won't update if you remove the key property from Router.

import React, { useState } from "react";
import { render } from "react-dom";
import { BrowserRouter, Link } from "react-router-dom";

const Root = () => {
  const [count, setCount] = useState(1);
  const basename = `basename-${count}`;

  return (
    <BrowserRouter basename={basename} key={basename}>
      <Link to="/link1">Link 1</Link>
      <br />
      <Link to="/link2">Link 2</Link>
      <br />
      Current basename: {basename}
      <br />
      <button onClick={() => setCount(count + 1)}>change basename</button>
    </BrowserRouter>
  );
};

render(<Root />, document.getElementById("root"));
like image 3
tuff Avatar answered Nov 20 '22 09:11

tuff