2

I'm trying to Implement Cam Jackson’s micro-frontend applications, so far I'm able to create 3 following react application:

  1. parent app
  2. child1 app
  3. child2 app

blog reference

The problem that I'm facing is with the routing. parent app routes:

const Child1 = ({ history }) => (
  <MicroFrontend history={history} host={childapp1} name="child1" />
);

const Child2 = ({ history }) => (
  <MicroFrontend history={history} host={childapp2} name="child2" />
);

const App = () => (
  <BrowserRouter>
  <Account>
    <React.Fragment>
    <ReactNotifications />
      <AppHeader />
      <Routes>
      <Route exact path="/" element={<Signin/>} />
        <Route exact path="/about" element={<About/>} />
        <Route exact path="/child1/*" element={<Child1/>} />
        <Route exact path="/child2/*" element={<Child2/>} />
      </Routes>
    </React.Fragment>
    </Account>
  </BrowserRouter>
);

Child app1 routes:

<BrowserRouter>
  <Account>
    <React.Fragment>
    <ReactNotifications />
      <Routes>
      <Route path='child1' element={<ProtectedRoute><Home/></ProtectedRoute>}/>
        <Route
          path="child1/dashboard"
          element={
            <ProtectedRoute>
              <Dashboard/>
            </ProtectedRoute>
          }
        />
        <Route path="child1/forbidden" element={<ForbiddenPage/>}
        />
      </Routes>
    </React.Fragment>
    </Account>
  </BrowserRouter>

So far all right, When i try to navigate from child1/any-route to '/' which is a signin route in parent. it tries to search for a component which is at '/' in the child app1 routes which seems to be a expected behaviour, what i want to understand is how can i navigate from childapp1's component to parent app's signin route.

i tried to navigate using useNavigate hook on token expiry by following code:

In child app1

const ProtectedRoute = ({ children }) => {
    const { authData } = useAuth()
    const navigate = useNavigate()
    
    console.log(authData, 'authData');

    if (authData.isAuthenticated && authData.expiryTime > Date.now()) {
        return children
    }
    else{
        navigate('/')
    }
};

export default ProtectedRoute;

expected workflow is: parent app's signin -> childapp1/home -> authToken expires -> navigate to parent app's signin

update: MicroFrontend.js

import React from 'react';

class MicroFrontend extends React.Component {
  componentDidMount() {
    const { name, host, document } = this.props;
    const scriptId = `micro-frontend-script-${name}`;

    if (document.getElementById(scriptId)) {
      this.renderMicroFrontend();
      return;
    }
    // if (document.getElementById(styleId)) {
    //   this.renderMicroFrontend();
    //   return;
    // }
    var parentUrl = `${window.location.protocol}//${window.location.href.split('/')[2]}`
    if (name === 'child1'){
      fetch(`${parentUrl}/child1/asset-manifest.json`,{
        method: "GET",
      })
        .then(res =>res.json())
        .then(manifest => {
          console.log(manifest, 'manifest');
          const script = document.createElement('script');
          const linkforCSSfile = document.createElement("link")
  
          linkforCSSfile.href = `${parentUrl}/child1${manifest['files']['main.css']}`
          linkforCSSfile.type = 'text/css'
          linkforCSSfile.rel = 'stylesheet'
  
          script.id = scriptId;
          script.crossOrigin = '';
          script.src = `${parentUrl}/child1${manifest['files']['main.js']}`;
          script.onload = this.renderMicroFrontend;
  
          document.head.appendChild(script);
          document.head.appendChild(linkforCSSfile)
        })
    }
    else{

      fetch(`${parentUrl}/child2/asset-manifest.json`,{
        method: "GET",
      })
        .then(res =>res.json())
        .then(manifest => {
          console.log(manifest, 'manifest');
          const script = document.createElement('script');
          const linkforCSSfile = document.createElement("link")
  
          linkforCSSfile.href = `${parentUrl}/child2${manifest['files']['main.css']}`
          linkforCSSfile.type = 'text/css'
          linkforCSSfile.rel = 'stylesheet'
  
          script.id = scriptId;
          script.crossOrigin = '';
          script.src = `${parentUrl}/child2${manifest['files']['main.js']}`;
          script.onload = this.renderMicroFrontend;
  
          document.head.appendChild(script);
          document.head.appendChild(linkforCSSfile)
    })
  }
    
      
  }

  componentWillUnmount() {
    const { name, window } = this.props;

    window[`unmount${name}`]?.(`${name}-container`);
  }

  renderMicroFrontend = () => {
    const { name, window, history } = this.props;
    console.log(history);

    window[`render${name}`]?.(`${name}-container`, history);
  };

  render() {
    return <main id={`${this.props.name}-container`} />;
  }
}

MicroFrontend.defaultProps = {
  document,
  window,
};

export default MicroFrontend;

Thanks in Advance.

  • 1
    What is `history` that is passed to the children route components? Do all the app's routers share a common history object reference? The "nested" routers should probably have a specified `basename` prop to match where they are located relative to the parent app/router. – Drew Reese Aug 01 '23 at 18:54
  • Thanks for pointing out, I have refered the the above mentioned blog code for my micro-frontend and it seems like i should not be passing history since i am using react-router-dom 6.14.1 where useHistory is replaced with useNavigate, and it also not possible to use useNavigate hook in my MicroFrontend class based component . however as you have pointed out, it is necessary to pass history to each child application. but can you help me figure out how can i achieve this. I have updated the question with MicroFrontend.js file for more context. Thank you. – Shashank Ky Aug 02 '23 at 08:45

1 Answers1

0

You might be able to get by with using the HistoryRouter with an instantiated BrowserHistory object in lieu of the BrowserRouter (which instantiates an internal history reference of its own). This method does require your project to include history@5 as a project dependency.

npm install --save history@5

Example:

import {
  unstable_HistoryRouter as HistoryRouter,
  Routes,
  Route
} from 'react-router-dom';
import { createBrowserHistory } from 'history';

const history = createBrowserHistory();

const App = () => (
  <HistoryRouter history={history}>
    <Account>
      <ReactNotifications />
      <AppHeader />
      <Routes>
        <Route path="/" element={<Signin />} />
        <Route path="/about" element={<About />} />
        <Route
          path="/child1/*"
          element={(
            <MicroFrontend
              history={history}
              host={childapp1}
              name="child1"
            />
          )}
        />
        <Route
          path="/child2/*"
          element={(
            <MicroFrontend
              history={history}
              host={childapp2}
              name="child2"
            />
          )}
        />
      </Routes>
    </Account>
  </HistoryRouter>
);

Then similarly both children apps would import the HistoryRouter and pass the passed history object as a prop so they all reference the same history.

Drew Reese
  • 165,259
  • 14
  • 153
  • 181