I am trying to generate routes with the appropriate components from "Feature" objects. The idea is to have a collection of site features that can be enabled or disabled, and routes/navigation menu items generated from this collection. Sort of like feature toggle for the routes and navigation items. I got as far as the page runs but any navigation just loads the first item from the list although the url gets updated in the browsers url bar.
Here is the "Feature" object:
export default class SiteFeature {
id: string;
name: string;
displayName: string;
path: string;
icon: string;
isEnabled: boolean;
isNavOption: boolean;
component: () => JSX.Element | undefined;
constructor(
id: string,
compononet: () => JSX.Element | undefined,
{ name = '', displayName = '', path = '', icon = '', isEnabled = false, isNavOpion = false } = {},
) {
this.id = id;
this.component = compononet;
this.name = name;
this.displayName = displayName;
this.path = path;
this.icon = icon;
this.isEnabled = isEnabled;
this.isNavOption = isNavOpion;
}
}
Here is the index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import './styles/index.scss';
import App from './App';
import { BrowserRouter as Router } from 'react-router-dom';
ReactDOM.render(
<React.StrictMode>
<Router>
<App />
</Router>
</React.StrictMode>,
document.getElementById('root'),
);
And here is the app.tsx
import React, { useEffect, useState } from 'react';
import { BrowserRouter as Router, Route, Redirect, Switch } from 'react-router-dom';
import './styles/App.scss';
import Footer from './components/Footer';
import Navigation from './components/Navigation';
import AboutPage from './components/pages/AboutPage';
import LandingPage from './components/pages/LandingPage';
import PageNotFound from './components/pages/PageNotFound';
import background from './images/background.jpg';
import { createStyles, makeStyles, Theme, ThemeProvider } from '@material-ui/core';
import siteThemeCollection from './styles/siteThemeCollection';
import ActualsPage from './components/pages/ActualsPage';
import EventsPage from './components/pages/EventsPage';
import DonationPage from './components/pages/DonationPage';
import ContactPage from './components/pages/ContactPage';
import getSiteFeatures from './services/featureToggle';
import SiteFeature from './common/SiteFeature';
const themeCollection = siteThemeCollection();
function App(): JSX.Element {
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
flexGrow: 1,
},
appContainer: {
backgroundImage: `url(${background})`,
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
backgroundSize: 'cover',
display: 'flex',
flexDirection: 'column',
minHeight: '100vh',
opacity: 1,
},
}),
);
const pageStyle = useStyles();
const [siteFeatures, setSiteFeatures] = useState<SiteFeature[]>([]);
useEffect(() => {
setSiteFeatures(getSiteFeatures);
}, []);
return (
<>
<ThemeProvider theme={themeCollection.defaultSiteTheme}>
<div className={pageStyle.appContainer}>
<div className="app-content">
<Switch>
<Route exact path="/">
<LandingPage />
</Route>
{siteFeatures.map((feature) => (
<div key={feature.id}>
{feature.isEnabled && <Route path={feature.path}>{feature.component}</Route>}
</div>
))}
<Route path="/404">
<PageNotFound />
</Route>
<Redirect to="/404" />
</Switch>
</div>
<Footer />
</div>
</ThemeProvider>
</>
);
}
export default App;
And here is the feature toggle collection
import SiteFeature from '../common/SiteFeature';
import AboutPage from '../components/pages/AboutPage';
import ActualsPage from '../components/pages/ActualsPage';
import EventsPage from '../components/pages/EventsPage';
const getSiteFeatures = (): SiteFeature[] => {
const siteEmabledFeatures: SiteFeature[] = [];
const inactive: SiteFeature[] = [];
siteEmabledFeatures.push(
new SiteFeature('null1', ActualsPage, {
name: 'actual',
displayName: 'Aktuális',
path: '/actuals',
icon: 'new_releases',
isEnabled: true,
isNavOpion: true,
}),
new SiteFeature('null2', AboutPage, {
name: 'about',
displayName: 'Bemutatkozás',
path: '/about',
icon: 'info',
isEnabled: true,
isNavOpion: true,
}),
new SiteFeature('null3', EventsPage, {
name: 'events',
displayName: 'Események',
path: '/events',
icon: 'events',
isEnabled: true,
isNavOpion: true,
}),
return siteEmabledFeatures;
};
export default getSiteFeatures;
I am a young padawan when it comes to react so i could be on the wrong track but any advise how to make this work would be greatly appreciated. Thanks.
The Switch
component really only has two valid children components: Route
and Redirect
. The Switch
will return and render the first "match" it finds, and in this case it is hitting the first child div
and rendering that.
<Switch>
<Route exact path="/">
<LandingPage />
</Route>
{siteFeatures.map((feature) => (
<div key={feature.id}> // <-- not a Route or Redirect so gets rendered
{feature.isEnabled && <Route path={feature.path}>{feature.component}</Route>}
</div>
))}
<Route path="/404">
<PageNotFound />
</Route>
<Redirect to="/404" />
</Switch>
Filter then map your routes.
<Switch>
<Route exact path="/">
<LandingPage />
</Route>
{siteFeatures.filter(({ isEnabled }) => isEnabled).map((feature) => (
<Route
key={feature.id}
path={feature.path}
component={feature.component}
/>
))}
<Route path="/404">
<PageNotFound />
</Route>
<Redirect to="/404" />
</Switch>