4

Relatively new to react and javascript, but was trying to organize a project with some routes being pulled from a different constant so I could use them in another file so that someone could develop and test on a separate module without needing the full application (pseudo micro frontends type stuff). this is likely over-engineering, but basically, when I put my routes in a const and reference them from somewhere else, any route below my const references just loads a blank page instead of the components or html.

Any guidance would be appreciated.

(react 17.0.2 and react-router-dom 5.1.2)

routes.js):

import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom";
import * as Yup from 'yup';
import Location from 'app-location';
import * as profile from '@profile-views';
import * as view from "modules/app/views";
...

const PROFILE_ROOT = `/profile`;

const userId = Yup.string();

/* Profile Routes */
export const Profile = new Location(
    `${PROFILE_ROOT}/:userId`, 
    { 
      userId: userId.required()
    }
  );
export const ProfileContactInfo = new Location(
  `${Profile.path}/contact`, 
  { userId: userId.required()}, 
);
export const ProfileCalendar = new Location(
  `${Profile.path}/calendar`, 
  { userId: userId.required()}, 
);
...
export const renderRoutes = (
  <>
      {/* everything works if Route components are in Routes() */}
    <Route exact path={Profile.path} component={profile.Timeline}/>
    <Route path={ProfileCalendar.path} component={profile.Calendar}/>
    <Route path={ProfileContactInfo.path} component={profile.ContactInfo}/>
  </>
)

export default function Routes() {
  return (
    <BrowserRouter>
      <Switch>
        <Route path="/login" component={view.Login} />
        { renderRoutes }
        {/* EVERYTHING ABOVE WORKS */}
        {/* EVERYTHING BELOW renderRoutes DOES NOT WORK */}
        {/* everything below works if i remove renderRoutes */}
        {/* everything works if i copy Routes from renderRoutes here */}
        <Route path="/create-user" component={view.CreateUser} />
        <Route path="/404" component={() => <h1>Not Found!</h1>} />
        <Redirect to="/404" />
      </Switch>
    </BrowserRouter>
  );
}

App.js:

import Routes from "./routes";
...
function App() {
    return (
        <UserProvider>
            <Routes />
        </UserProvider>
    );
}
eisbaer
  • 328
  • 1
  • 3
  • 10
  • Could you edit the post and give values of Profile.path like stuff you have imported , so that we can understand the problem better – Goutham J.M Apr 17 '21 at 18:40

3 Answers3

1

answer found here: https://github.com/ReactTraining/react-router/issues/5785

Switch component doesn't like react fragments as children. Work-around appears to be adding a wrapper component to Switch that removes the fragments.

updated the following per the link and everything works fine.

import React, { Fragment } from 'react';

...

export const FragmentSupportingSwitch = ({children}) => {
  const flattenedChildren = [];
  flatten(flattenedChildren, children);
  return React.createElement.apply(React, [Switch, null].concat(flattenedChildren));
}

function flatten(target, children) {
  React.Children.forEach(children, child => {
    if (React.isValidElement(child)) {
      if (child.type === Fragment) {
        flatten(target, child.props.children);
      } else {
        target.push(child);
      }
    }
  });
}

...

export default function Routes() {
  return (
    <BrowserRouter>
      <FragmentSupportingSwitch>
        <Route path="/login" component={view.Login} />
        { renderRoutes }
        <Route path="/create-user" component={view.CreateUser} />
        <Route path="/404" component={() => <h1>Not Found!</h1>} />
        <Redirect to="/404" />
      </FragmentSupportingSwitch>
    </BrowserRouter>
  );
}
eisbaer
  • 328
  • 1
  • 3
  • 10
1

The problem is related to the React Fragment you are using inside variable render routes <></> Switch that you have wrapped only works with the first level of components directly under it. We can't traverse the entire tree. one way to fix it is to remove switch and just use routes , what switch does is it will return only one component so removing it or wrapping switch only around renderRoutes like this

First Solution

<BrowserRouter>
<Switch>{renderRoutes()}</Switch>
<Routes path=/ component={Home}/>
</BrowserRouter>

another way is to implement the below function

Second Solution

you can use two function to allow Fragment to be supported in Switch using the below code ,by wrapping FragmentSupportingSwitch , you can solve it




function FragmentSupportingSwitch({ children }) {
  const flattenedChildren = [];
  flatten(flattenedChildren, children);
  return React.createElement.apply(
    React,
    [Switch, null].concat(flattenedChildren)
  );
}

function flatten(target, children) {
  React.Children.forEach(children, (child) => {
    if (React.isValidElement(child)) {
      if (child.type === React.Fragment) {
        flatten(target, child.props.children);
      } else {
        target.push(child);
      }
    }
  });
}


export default function App() {
  return (
    <div className="App">
      <BrowserRouter>
        <Switch>
          <FragmentSupportingSwitch>
            {commonRoute}
            <Route path="/" component={Home} />
            <Route path="/apple" component={Apple} />
          </FragmentSupportingSwitch>
        </Switch>
      </BrowserRouter>
    </div>
  );
}


You can check out this code sandbox for reference

Edit throbbing-thunder-59mdl

Goutham J.M
  • 1,726
  • 12
  • 25
0

renderRoutes function actually its a father Route

<Route path={PROFILE_ROOT} component={RenderRoutes} />

const RenderRoutes = () => (
  <Switch>
    <Route exact path={Profile.path} component={profile.Timeline}/>
    <Route path={ProfileCalendar.path} component={profile.Calendar}/>
    <Route path={ProfileContactInfo.path} component={profile.ContactInfo}/>
  </Switch>
)
Hagai Harari
  • 2,767
  • 3
  • 11
  • 23