Component stopped to re-render after using privateroute - reactjs

I am making the first application in Reactjs (simple shop) and have some problems with rerendering UI after changing state values. Everything worked correctly before using PrivateRoute inside App.js. Now when I am clicking 'Add to card', product state changing like it should (quantity set to 1), but on UI it's still 0 (so the component is not re-rendered).
Maybe it's because I am using component={ProductList} instead of render={() => } inside App.js routing. But render don't work anymore with PrivateRoute (errors are thrown). I am not sure what I am doing wrong, but as I sad this is my first days of writing anything in reactjs so I need a little help.
App.js
import React from 'react';
import './../App.css';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import Navbar from '../components/navbar';
import ProductList from '../components/productList';
import Checkout from '../components/checkout';
import Payment from '../components/payment';
import Footer from '../components/footer';
import SignIn from '../components/auth/signIn';
import SignUp from '../components/auth/signUp';
import { PrivateRoute } from '../components/auth/privateRoute';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
loggedIn: true,
selectedProducts: [],
products: [
{
"id": 1,
"name": "11 bit studios SA",
"grossPrice": 390.00,
"netPrice": 360.00,
"image": "https://jarock.pl/upload/posts/836323184026f846637c9e455b3950a4.jpg",
"description": "",
"quantity": 0
},
{
"id": 2,
"name": "PEKAO",
"grossPrice": 108.00,
"netPrice": 100.00,
"image": "https://pbs.twimg.com/profile_images/1107386368609652736/U91cV_vU.png",
"description": "",
"quantity": 0
},
{
"id": 3,
"name": "CDPROJEKT",
"grossPrice": 198.00,
"netPrice": 170.00,
"image": "https://yt3.ggpht.com/a/AGF-l7_JugJ8uDvDTXqsBPLuT4ZueARyKoM1dVG1gA=s900-mo-c-c0xffffffff-rj-k-no",
"description": "",
"quantity": 0
},
{
"id": 4,
"name": "CCC",
"grossPrice": 147.00,
"netPrice": 135.00,
"image": "https://ccc.eu/start_page/assets/img/logo.jpg",
"description": "",
"quantity": 0
}
]
}
this.AddItemToSelectedProductList = this.AddItemToSelectedProductList.bind(this);
this.RemoveItemFromSelectedProductList = this.RemoveItemFromSelectedProductList.bind(this);
}
AddItemToSelectedProductList(newProduct) {
var newProductList = this.state.selectedProducts;
const existingProduct = newProductList.find(item => newProduct.id === item.id);
if (existingProduct) {
existingProduct.quantity++;
this.setState((state) => ({
products: state.products
}));
}
else {
newProduct.quantity = 1;
newProductList.push(newProduct);
this.setState({ selectedProducts: newProductList });
}
};
RemoveItemFromSelectedProductList(productToRemove) {
var newProductList = this.state.selectedProducts;
if (newProductList.length > 0) {
const productToRemoveIndex = newProductList.indexOf(productToRemove);
newProductList.splice(productToRemoveIndex, 1);
this.setState({ selectedProducts: newProductList });
}
var displayedProductList = this.state.products;
const displayedProduct = displayedProductList.find(x => x.id === productToRemove.id);
displayedProduct.quantity = 0;
this.setState({ products: displayedProductList });
};
render() {
return (
<Router>
<div className="main-div">
<Navbar checkoutItems={this.state.selectedProducts} />
<PrivateRoute exact path='/' component={ProductList} products={this.state.products} selectProductHandler={this.AddItemToSelectedProductList} />
<PrivateRoute path="/checkout" component={Checkout} selectedProducts={this.state.selectedProducts} removeProductHandler={this.RemoveItemFromSelectedProductList} />
<PrivateRoute path="/payment" component={Payment} />
<Route path="/signin" component={SignIn} />
<Route path="/signup" component={SignUp} />
</div>
<Footer />
</Router>
);
}
}
export default App;
PrivateRoute.js
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
export const PrivateRoute = ({ component, redirectTo, ...rest }) => {
return (
<Route {...rest} render={routeProps => {
return localStorage.getItem('user') ? (
renderMergedProps(component, routeProps, rest)
) : (
<Redirect to={{
pathname: '/signin',
state: { from: routeProps.location }
}} />
);
}} />
);
};
const renderMergedProps = (component, ...rest) => {
const finalProps = Object.assign({}, ...rest);
return (
React.createElement(component, finalProps)
);
}
export const PropsRoute = ({ component, ...rest }) => {
return (
<Route {...rest} render={routeProps => {
return renderMergedProps(component, routeProps, rest);
}} />
);
}
Before (when everything worked perfectly) I didn't use PrivateRoute inside App.js. The route to my product list looked like that:
<Route exact path="/" render={() => <ProductList products={this.state.products} selectProductHandler={this.AddItemToSelectedProductList} />} />
After #Sky suggestions I changed the implementation of 'add to card'. I used immerjs to make it simply. After all changes everything works like before, UI is not re-rendered. Here is changed code:
AddItemToSelectedProductList(newProduct) {
this.AddToSelectedProducts(newProduct);
this.AddToProducts(newProduct);
};
AddToProducts(newProduct) {
const nextState = produce(this.state.products, draft => {
const productIndex = draft.findIndex(item => item.id === newProduct.id);
draft[productIndex].quantity = draft[productIndex].quantity + 1;
});
this.setState({ products: nextState });
}
AddToSelectedProducts(newProduct) {
var newProductList = this.state.selectedProducts;
const existingProduct = newProductList.find(item => newProduct.id === item.id);
var nextState = [];
if (existingProduct) {
nextState = produce(newProductList, draft => {
const existingProductIndex = draft.findIndex(item => item.id === newProduct.id);
draft[existingProductIndex].quantity = draft[existingProductIndex].quantity + 1;
});
}
else {
const nextProductState = produce(newProduct, draft => {
draft.quantity = 1;
});
nextState = produce(newProductList, draft => {
draft.push(nextProductState);
});
}
this.setState({ selectedProducts: nextState });
};

In React, object mutations are not allowed, and you must create a new object copy every time.
existingProduct.quantity++;
should be
const newProduct = {...existingProduct, quantity: existingProduct.quantity + 1}
this.setState((state) => ({
products: state.products
}));
Similar problem here, you should create a new instance of an array.

After modifying AddToProducts like this UI re-renders correctly again:
AddToProducts(newProduct) {
var newProductList = this.state.products.slice();
const productIndex = newProductList.findIndex(item => item.id === newProduct.id);
newProductList[productIndex].quantity = newProductList[productIndex].quantity + 1;
this.setState({ products: newProductList });
}
I am not sure why this not worked (immerjs):
AddToProducts(newProduct) {
const nextState = produce(this.state.products, draft => {
const productIndex = draft.findIndex(item => item.id === newProduct.id);
draft[productIndex].quantity = draft[productIndex].quantity + 1;
});
this.setState({ products: nextState });
}

Related

Make react-router-dom v6 pass path as key to rendered element

I think I may need a paradigm shift in my thinking here, so I'm open to those kinds of answers as well here.
Consider the following simplified example:
export const App = (p: { apiClient: SomeApiClient }) => {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/posts/:postId" element={<Post apiClient={p.apiClient} />} />
</Routes>
);
}
export const Home = () => {
return <h1>Home!</h1>
}
export const Post = (p: { apiClient: SomeApiClient }) => {
const { postId } = useParams();
const [ state, setState ] = useState<PostState>({ status: "loading" });
// When the component mounts, get the specified post from the API
useEffect(() => {
if (state.status === "loading") {
(async () => {
const post = await p.apiClient.getPost(postId);
setState({ status: "ready", post });
})();
}
})
return (
<h2>Posts</h2>
{
state.status === "loading"
? <p>Loading....</p>
: <div className="post">
<h3>{state.post.title}</h3>
<div className="content">{state.post.content}</div>
</div>
}
)
}
export type PostState =
| { status: "loading" }
| { status: "ready"; post: BlogPost };
export type BlogPost = { title: string; content: string };
This works fine the first time, but pretend there's a <Link /> on the page that goes to the next post. When I click that link, the URL changes, but the page content doesn't, because React Router is not actually re-mounting the <Post .../> component. That component correctly receives the updated postId and is re-rendered, but since it doesn't get re-mounted, the useEffect logic doesn't run again and the content stays the same.
I've been solving this very awkwardly by creating intermediary components like so:
export const App = (p: { apiClient: SomeApiClient }) => {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/posts/:postId" element={<PostRenderer apiClient={p.apiClient} />} />
</Routes>
);
}
export const PostRenderer = (p: { apiClient: SomeApiClient }) => {
const { postId } = useParams();
return <Post key={postId} postId={postId} apiClient={p.apiClient} />
}
export const Post = (p: { postId: string; apiClient: SomeApiClient }) => {
// ....
}
But I'm starting to get a lot of those, and literally all they do is take the param from the URL and use it as a key on the actual target component. I've read through the react-router-dom docs and am not finding anything that indicates there's a way to automate this. I must be thinking about this wrong.... Any suggestions are appreciated.
I think a more common and practical solution is to add the postId as a dependency to the useEffect to rerun the asynchronous logic when the route param changes.
Example:
export const Post = (p: { apiClient: SomeApiClient }) => {
const { postId } = useParams();
const [state, setState] = useState<PostState>({ status: "loading" });
// When the post id updates, get the specified post from the API
useEffect(() => {
const fetchPostById = async (postId) => {
setState({ status: "loading" });
const post = await p.apiClient.getPost(postId);
setState({ status: "ready", post });
};
fetchPostById(postId);
}, [postId]);
return (
<h2>Posts</h2>
{
state.status === "loading"
? <p>Loading....</p>
: <div className="post">
<h3>{state.post.title}</h3>
<div className="content">{state.post.content}</div>
</div>
}
)
};

Update children's state in higher parent component

I need some help. I have a wrapper App component where I hold the state of the application, the App component renders a Router component. I need to set in every page of the application the changes that were made on the page. Every page has a next button to the next page using NavLinks.
I tried to pass down a handle change method but I can not get it to work, the handleChange doesn't get fired. Any help would be very much appreciated.
App:
export default class App extends React.Component {
state = {
categoriesWithSub: [],
currentPage: '',
ticket: {
departments: '',
request: '',
exactRequest: '',
attachments: '',
},
};
componentDidMount = async () => {
const categories = await getCategories();
const categoriesWithSub = await Promise.all(
categories.map(async category => {
const subCategories = await getSubCategories(category.id);
return { ...category, subCategories };
}),
);
this.setState({ categoriesWithSub });
};
makeHandleChange = () => {
alert('Test!');
/* this.setState({
ticket: { ...this.state.ticket, [pageName]: pageChanges },
}); */
};
render() {
const { categoriesWithSub } = this.state;
return <Router categoriesWithSub={categoriesWithSub} handleChange={this.makeHandleChange} />;
}
}
Router:
const routes = [
{
path: '/',
exact: true,
component: Home,
},
{
path: '/departments',
exact: true,
component: Departments,
},
{
path: '/request',
exact: true,
component: Request,
},
{
path: '/thank-you',
exact: true,
component: ThankYou,
},
{
path: '/success',
exact: true,
component: Success,
},
];
export default function Router(categoriesWithSub, handleChange) {
return (
<Switch>
{routes.map(({ path, exact, component: Component, layoutProps = {} }) => {
const WithLayout = ({ ...props }) => (
<Layout {...layoutProps}>
<Component {...props} />
</Layout>
);
return (
<Route
key={path}
path={path}
exact={exact}
render={() => <WithLayout categoriesWithSub={categoriesWithSub} handleChange={handleChange} />}
/>
);
})}
</Switch>
);
}
Child component:
export default class extends Component {
state = {
};
componentDidMount() {
this.props.handleChange(this.state);
}
render() {
const { categoriesWithSub } = this.props.categoriesWithSub;
return (
{categoriesWithSub &&
categoriesWithSub.map(item => (
<Tile
to="./request"
department={item.catName}
key={item.id}
/>
))}
</div>
);
}
}
export function Tile({ to, department }) {
return (
<NavLink to={to} css={tile}>
{department}
</NavLink>
);
}
This will not work
export default function Router(categoriesWithSub, handleChange)
That should be
export default function Router({categoriesWithSub, handleChange})
//or
export default function Router(props){
const {categoriesWithSub, handleChange} = props;
}

Prevent page reload when rendering ui fabric react nav component

I'm stuck trying to get the ui-fabric Nav component working with react-router-dom v4+. My solution "works", but the whole page is rerendered instead of just the NavSelection component. After some research i realize i need to do a e.preventDefault() somewhere, but i can't figure out where to add it.
Main Page:
export const Home = () => {
return (
<div className="ms-Grid-row">
<div className="ms-Grid-col ms-u-sm6 ms-u-md4 ms-u-lg2">
<Navbar />
</div>
<div className="ms-Grid-col ms-u-sm6 ms-u-md8 ms-u-lg10">
<NavSelection />
</div>
</div>
);
}
Navbar:
const navGroups = [
{
links: [
{ name: 'Name1', url: '/Name1', key: '#Name1' },
{ name: 'Name2', url: '/Name2', key: '#Name2' }
]
}
];
export class Navbar extends React.Component<any, any> {
constructor(props: INavProps) {
super(props);
this.state = {
selectedNavKey: '#Name1'
};
}
public componentDidMount() {
window.addEventListener('hashchange', (e) => {
this.setState({ selectedNavKey: document.location.hash || '#' });
});
}
public render(): JSX.Element {
const { selectedNavKey } = this.state;
return (
<Nav
selectedKey={selectedNavKey}
groups={navGroups}
/>
);
}
}
NavSelection:
export const NavSelection = () => {
return (
<div>
<Route path="/Name1" component={Component1} />
<Route path="/Name2" component={Component2} />
</div>
);
}
Any help is greatly appreciated
Edit: I've tried to put it inside componentDidMount like this:
public componentDidMount() {
window.addEventListener('hashchange', (e) => {
e.preventDefault();
this.setState({ selectedNavKey: document.location.hash || '#' });
});
}
That does not work.
Use the HashRouter instead of the BrowserRouter.
Example:
Router:
...
import { Switch, Route, Redirect, HashRouter } from 'react-router-dom'
...
export const Router: React.FunctionComponent = () => {
// persisted to localStorage
const navActiveItem = useSelector(selectNavActiveItem)
return (
<Suspense fallback={<LargeSpinner />}>
<HashRouter>
<Switch>
<Route exact path="/" render={() => (
<Redirect to={navActiveItem.url} />
)}/>
<Route exact path="/dashboard/overview" component={Overview} />
<Route exact path="/dashboard/progress" component={Progress} />
<Route exact path="/dashboard/issues" component={Issues} />
...
</Switch>
</HashRouter>
</Suspense>
)
}
Navigation:
...
const navLinkGroups: INavLinkGroup[] = [
{
name: 'Dashboard',
expandAriaLabel: 'Expand Dashboard section',
collapseAriaLabel: 'Collapse Dashboard section',
links: [
{
key: 'DashboardOverview',
name: 'Overview',
icon: 'BIDashboard',
url: '#/dashboard/overview',
},
{
key: 'DashboardProgress',
name: 'Progress',
icon: 'TimelineProgress',
url: '#/dashboard/progress',
},
{
key: 'DashboardIssues',
name: 'Issues',
icon: 'ShieldAlert',
url: '#/dashboard/issues',
},
],
},
...
export const Navigation: React.FunctionComponent = () => {
const navActiveItem = useSelector(selectNavActiveItem)
const dispatch = useDispatch()
const onLinkClick = (ev?: React.MouseEvent<HTMLElement>, item?: INavLink) => {
dispatch(setNavActiveItem(item || { name: '', url: '/' }))
}
return (
<Stack tokens={stackTokens} styles={stackStyles}>
<Nav
styles={navStyles}
ariaLabel="Navigation"
groups={navLinkGroups}
onLinkClick={onLinkClick}
initialSelectedKey={navActiveItem.key}
/>
</Stack>
)
}
I'm guessing you are using Microsoft's https://developer.microsoft.com/en-us/fabric#/components/nav#Variants
In that case you need to specify the callback on the nav item. Usually it's anti-pattern to use things like window.addEventListener in react.
This would look something like.
export class Navbar extends React.Component<any, any> {
constructor(props: INavProps) {
super(props);
this.state = {
selectedNavKey: '#Name1'
};
}
public handleNavClick(event, { key, url }) {
// You also need to manually update the router something like
history.push(url);
this.setState({ selectedNavKey: key });
}
public render(): JSX.Element {
const { selectedNavKey } = this.state;
return (
<Nav
selectedKey={selectedNavKey}
groups={{
links: [
{ name: 'Name1', url: '/Name1', key: '#Name1', onClick: this.handleNavClick },
{ name: 'Name2', url: '/Name2', key: '#Name2', onClick: this.handleNavClick }
]
}}
/>
);
}
}
To prevent page refresh, call event.preventDefault() inside Nav component's onLinkClick event handler:
<Nav onLinkClick={linkClickHandler} selectedKey={selectedKey} />
function linkClickHandler(event,{key, url}){
event.preventDefault();
setSelectedKey(key);
console.log(url);
}

this.props.history.push not re-rendering react component

In my component I use this.props.history.push(pathname:.. search:..) to rerender the component and fetch new data form a third party service. When I first call the page it renders. But when I call history push inside the component the URL updates correctly BUT the component doesn't rerender. I read a lot but couldn't get it working. Any ideas?
I'm using react router v4
//index.js
<Provider store={store}>
<BrowserRouter>
<Switch>
<Route path="/login" component={Login}/>
<Route path="/" component={Main}/>
</Switch>
</BrowserRouter>
</Provider>
//Main.js
//PropsRoute is used to push props to logs component so I can use them when fetching new data
const PropsRoute = ({ component: Component, ...rest }) => {
return (
<Route {...rest} render={props => <Component {...props} />}/>
);
};
class Main extends Component {
render() {
return (
<div className="app">
<NavigationBar/>
<div className="app-body">
<SideBar/>
<Switch>
<PropsRoute path="/logs" component={Log}/> //this component is not rerendering
<Route path="/reports" component={Reports}/>
<Route path="/gen" component={Dashboard}/>
<Redirect from="/" to="/gen"/>
</Switch>
</div>
</div>
)
}
}
export default Main;
//inside 'Log' component I call
import React, {Component} from 'react';
import {getSystemLogs} from "../api";
import {Link} from 'react-router-dom';
import _ from "lodash";
import queryString from 'query-string';
let _isMounted;
class Log extends Component {
constructor(props) {
super(props);
//check if query params are defined. If not re render component with query params
let queryParams = queryString.parse(props.location.search);
if (!(queryParams.page && queryParams.type && queryParams.pageSize && queryParams.application)) {
this.props.history.push({
pathname: '/logs',
search: `?page=1&pageSize=25&type=3&application=fdce4427fc9b49e0bbde1f9dc090cfb9`
});
}
this.state = {
logs: {},
pageCount: 0,
application: [
{
name: 'internal',
id: '...'
}
],
types: [
{
name: 'Info',
id: 3
}
],
paginationPage: queryParams.page - 1,
request: {
page: queryParams.page === undefined ? 1 : queryParams.page,
type: queryParams.type === undefined ? 3 : queryParams.type,
pageSize: queryParams.pageSize === undefined ? 25 : queryParams.pageSize,
application: queryParams.application === undefined ? 'fdce4427fc9b49e0bbde1f9dc090cfb9' : queryParams.application
}
};
this.onInputChange = this.onInputChange.bind(this);
}
componentDidMount() {
_isMounted = true;
this.getLogs(this.state.request);
}
componentWillUnmount() {
_isMounted = false;
}
getLogs(request) {
getSystemLogs(request)
.then((response) => {
if (_isMounted) {
this.setState({
logs: response.data.Data,
pageCount: (response.data.TotalCount / this.state.request.pageSize)
});
}
});
}
applyFilter = () => {
//reset page to 1 when filter changes
console.log('apply filter');
this.setState({
request: {
...this.state.request,
page: 1
}
}, () => {
this.props.history.push({
pathname: '/logs',
search: `?page=${this.state.request.page}&pageSize=${this.state.request.pageSize}&type=${this.state.request.type}&application=${this.state.request.application}`
});
});
};
onInputChange = () => (event) => {
const {request} = this.state; //create copy of current object
request[event.target.name] = event.target.value; //update object
this.setState({request}); //set object to new object
};
render() {
let logs = _.map(this.state.logs, log => {
return (
<div className="bg-white rounded shadow mb-2" key={log.id}>
...
</div>
);
});
return (
<main className="main">
...
</main>
);
}
}
export default Log;
Reactjs don't re-run the constructor method when just props or state change, he call the constructor when you first call your component.
You should use componentDidUpdate and do your fetch if your nextProps.location.pathname is different than your this.props.location.pathname (react-router location)
I had this same issue with a functional component and I solved it using the hook useEffect with the props.location as a dependency.
import React, { useEffect } from 'react';
const myComponent = () => {
useEffect(() => {
// fetch your data when the props.location changes
}, [props.location]);
}
This will call useEffect every time that props.location changes so you can fetch your data. It acts like a componentDidMountand componentDidUpdate.
what about create a container component/provider with getderivedstatefromprops lifecycle method, its more react-look:
class ContainerComp extends Component {
state = { needRerender: false };
static getderivedstatefromprops(nextProps, nextState) {
let queryParams = queryString.parse(nextProps.location.search);
if (!(queryParams.page && queryParams.type && queryParams.pageSize && queryParams.application)) {
return { needRefresh: true };
} else {
return { needRefresh: false };
}
}
render() {
return (
<div>
{this.state.needRefresh ? <Redirect params={} /> : <Log />}
</div>
);
}
}

React-Router 1.0.0-rc4 - dynamic router - params not working

Problem:
This dynamic router works, except when there is a dynamic link involving params.
Specifically:
I can hard code a link or type in the browser:
<Link to="Inventory/All-Vehicles">All Vehicles</Link>
http://localhost:3000/Inventory/All-Vehicles
And with code:
const { id } = this.props.params;
console.log({ id } );
The console shows:
{id: "All-Vehicles"}
However, with a dynamic link ...
< Link to={ this.props.item.linkTo } className="">{ this.props.item.title }< /Link>
which produces:
< a class="" href="#Inventory/All-Vehicles" data-reactid=".0.0.0.0.0.0.0.0.0.0.$2.1.0.$0.0.0.0">All Vehicles< /a>
the browser shows
localhost:3000/#/Inventory/All-Vehicles
for an instant and then resets itself to (the page does not reload)
localhost:3000/#/Inventory
With console showing:
Object {id: undefined}
I re-wrote this question per Jordan's suggestion below.
I hope it is not too lengthy.
I use alt flux as my store
routes.js:
import React, { Component, PropTypes } from 'react';
import Router, { Route } from 'react-router';
// alt
import connectToStores from '../../node_modules/alt/utils/connectToStores';
import NavStore from '../alt/stores/nav-store';
import NavActions from '../alt/actions/nav-actions';
class Routes extends Component {
constructor(props) {
super(props);
this.state = {
routes: []
};
}
static getStores() {
return [NavStore];
}
static getPropsFromStores() {
return NavStore.getState();
}
componentDidMount() {
const clientId = this.props.clientId;
NavActions.getAll(clientId);
}
fetchNonRootComponent(paths) {
let result;
paths.map((path) => {
if (path !== '/') {
result = path;
}
});
return result;
}
fetchMenuSystem(data) {
const self = this;
const currRoutesState = this.state.routes;
const routes = data === undefined ? this.props.items : data;
routes.map((route) => {
// set paths up first
let currPaths = [];
if (route.paths !== undefined) {
currPaths = route.paths;
} else {
currPaths.push(route.linkTo);
}
// Components - first check for ecomMods
let currComponent;
If it is in the routes.js file it probably has something to do this section:
if (route.ecomMod !== undefined) {
currComponent = require('../components/pages/' + route.ecomMod);
// clear out currPath if this is an ecom Module
// and start a new currPaths array
currPaths = [];
if (route.parentId === null) {
currPaths.push(route.ecomMod);
} else {
// multi-purpose :id, eg.
// Inventory/Used-Vehicles
// Inventory/Stock#1234
currPaths.push(route.ecomMod + '/:id');
}
} else {
const nonRootComponent = self.fetchNonRootComponent(currPaths);
currComponent = require('../components/pages/' + nonRootComponent);
}
currPaths.map((currPath) => {
const props = { key: currPath, path: currPath, component: currComponent };
currRoutesState.push(<Route { ...props } />);
});
if (route.childNodes !== undefined) {
self.fetchMenuSystem(route.childNodes);
}
});
return currRoutesState;
}
fetchRoutes() {
const result = this.fetchMenuSystem();
return (
<Route component={ require('../components/APP') }>
{ result }
<Route path="SiteMap" component={ require('../components/pages/Site-Map') }/>
<Route path="*" component={ require('../components/pages/Not-Found') }/>
</Route>
);
}
render() {
if (this.props.items.length === 0) return <div>Loading ...</div>;
const routerProps = {
routes: this.fetchRoutes(),
createElement: (component, props) => {
return React.createElement(component, { ...props });
}
};
return (
<Router { ...routerProps } history= { this.props.history } />
);
}
}
Routes.propTypes = {
clientId: PropTypes.string.isRequired,
history: PropTypes.object.isRequired,
items: PropTypes.array.isRequired
};
export default connectToStores(Routes);
navItems.json:
{
"data": [
{
"id": 1,
"parentId": null,
"linkTo": "/",
"paths": [
"/",
"Home"
],
"title": "Home",
},
{
"id": 2,
"parentId": null,
"linkTo": "About-Us",
"title": "About Us",
},
{
"id": 3,
"parentId": null,
"ecomMod": "Inventory",
"linkTo": "Inventory",
"title": "Inventory",
"childNodes": [
{
"id": 30,
"parentId": 3,
"ecomMod": "Inventory",
"linkTo": "Inventory/All-Vehicles",
"title": "All Vehicles",
}
]
}
]
}
SOLVED
Nearly a day later, I solved this and the mistake I made was so stupid and so blatantly obvious I cannot believe I did not see it.
As I suspected, the dynamic router is fine. The problem was with the drop down menu. Which I suspected as a hard-coded link on a page worked.
Illustratively, this is how the Inventory route is created:
<Route path="Inventory" component="Inventory">
<Route path="Inventory/All-Vehicles" component="Inventory" />
</Route>
So it is plain to anyone that an All-Vehicles click will 'bubble up' to it's parent, if the dam parent has a route handler event to it, and that is exactly what mine had.
So, in my case, this parent link:
<li id={ this.props.item.id }
......
onClick={ this.routeHandler.bind(this, { newLinkTo } ) } >
<span className="">{ this.props.item.title }</span>
// get the children
<div>{ this.fetchSubMenu(this.props.item) }</div>
</li>
is now:
<li id={ this.props.item.id }
......
>
<Link to={ newLinkTo } className="">{ this.props.item.title }</Link>
// get the children
<div>{ this.fetchSubMenu(this.props.item) }</div>
</li>
Lesson learned: If you have a route handler up the node tree, it will take over any route changes the children try to make.

Resources