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

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.

Related

Component stopped to re-render after using privateroute

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 });
}

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>
);
}
}

My component isn't updating, am I mutating the state?

I have a container component that connects to the state which I made with immutable.js. When I update the state, my redux inspector tells me that the state is updated, but my component doesn't get the new updates and doesn't re-render.
My container component:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { setCategoryActive } from '../actions'
import Category from '../components/Category'
class MenuList extends Component {
constructor(props) {
super(props)
this.categories = this.props.categories
this.subreddits = this.props.subreddits
this.allCategories = this.categories.get("allCategories")
this.byName = this.categories.get("byName")
}
printSubreddits(category) {
const subreddits = this.byName.get(category)
const subredditsByName = subreddits.get("subredditsByName")
const list = subredditsByName.map((subreddit, i) => {
return <p className="swag" key={i}>{subreddit}</p>
})
return list
}
isCategoryActive(category) {
const cat = this.byName.get(category)
return cat.get("active")
}
printCategory(category, i) {
console.log(this.isCategoryActive(category))
return (
<div className="category-container" key={i}>
<Category name={category}
active={this.isCategoryActive(category)}
setActive={this.props.setCategoryActive.bind(this, category)} />
{this.isCategoryActive(category) ? this.printSubreddits(category) : null}
</div>
)
}
render() {
return (
<div>
{this.allCategories.map((category, i) => {
const x = this.printCategory(category, i)
return x
}, this)}
</div>
)
}
}
const mapStateToProps = (state) => ({
categories: state.subredditSelector.get('categories'),
subreddits: state.subredditSelector.get('subreddits')
})
export default connect(mapStateToProps, {
setCategoryActive
})(MenuList);
My Category component
class Category extends Component {
printToggle(active) {
if (active) {
return <span> [-]</span>
} else {
return <span> [+]</span>
}
}
componentWillReceiveProps(nextProps) {
this.printToggle(nextProps.active)
}
render() {
const { setActive, active, name } = this.props
return (
<div className="category-container">
<a onClick={setActive}
href="#"
className="category-title">
{name}
{this.printToggle(active)}
</a>
</div>
)
}
}
export default Category
And my reducer
import { fromJS } from 'immutable'
import {
SET_SUBREDDIT_ACTIVE,
SET_CATEGORY_ACTIVE
} from '../actions'
import List from '../data/nsfw_subreddits.js'
const initState = fromJS(List)
const subredditSelector = (state = initState, action) => {
switch (action.type) {
case SET_SUBREDDIT_ACTIVE:
return state
case SET_CATEGORY_ACTIVE:
return state.updateIn(['categories', 'byName', action.payload.name],
x => x.set('active', !x.get('active')))
default:
return state
}
}
export default subredditSelector
A piece of my state that I have coded as a JSON object
const list = {
categories: {
byName: {
"example": {
name: "example",
id: 1,
active: true,
subredditsByName: ["example", "example"]
},
"example": {
name: "example",
id: 2,
active: true,
subredditsByName: ["example"]
},
"example": {
name: "example",
id: 3,
active: true,
subredditsByName: ["example", "example", "example"]
}
},
allCategories: ["example", "example", "example"]
},
subreddits: {
My guess is that my reducer is mutating the state? Though I am not sure how, since I am using the immutable js functions?
So I fixed this issue by changing the constructor from a constructor to a regular function and called it at the top of my render method!
You should still keep your constructor, but only have in it what needs to be done when the object is first created:
constructor(props) {
super(props)
}
And yes, having
cleanPropData(){
this.categories = this.props.categories
this.subreddits = this.props.subreddits
this.allCategories = this.categories.get("allCategories")
this.byName = this.categories.get("byName")
}
is fine. I haven't heard of someone invoking it at the top of a render function. Nice to know that works.

Dynamic React-Router 1.0.0-rc3

Regardless of what I have read from the folks at react-router, I
prefer my app's router have dynamic data, and I have been successful
in doing it, with one caveat: I cannot loop recursively for child
routes.
Here is a working dynamic react-router:
export default class Index extends React.Component {
constructor() {
super();
this.state = {
navItems: [] };
}
componentWillMount() {
NavMenuAPI.getAll()
.then((response) => {
const data = response.data;
this.setState({ navItems: data });
});
}
fetchMenuSystem(data) {
const routes = [];
data.map(function(route) {
let routePaths = [];
let component = {};
if (route.linkTo === '/') {
const pageApp = route.title.replace(/ /g, '-').toLocaleLowerCase();
component = require('./components/pages/' + pageApp);
} else {
const pageApp = route.title.replace(/ /g, '-').toLocaleLowerCase();
component = require('./components/pages/' + pageApp);
}
if (route.paths === undefined) {
routePaths.push(route.linkTo);
} else {
routePaths = JSON.parse(JSON.stringify(route.paths));
}
routePaths.map(function(path) {
const props = { key: path, path, component };
// Static `onEnter` is defined on
// component, we should pass it to route props
// if (component.onEnter) props.onEnter = component.onEnter;
routes.push(<Route { ...props } />);
});
//////////////////////////////////////////////
// PROBLEM !!!!!!!!!!
// if (route.childNodes !== undefined) {
// this.fetchMenuSystem(route.childNodes);
// }
});
return routes;
}
fetchRoutes() {
const data = this.state.navItems;
const result = this.fetchMenuSystem(data);
return (
<Route component={ require('./components/APP') }>
{ result }
<Route path="*" component={ require('./components/pages/not-found') }/>
</Route>
);
}
render() {
if (this.state.navItems.length === 0) return <div>Loading ...</div>;
const routerProps = {
routes: this.fetchRoutes(),
history: createHistory({
queryKey: false
}),
createElement: (component, props) => {
return React.createElement(component, { ...props });
}
};
return (
<Router { ...routerProps } />
);
}
}
ReactDOM.render(<Index />, document.getElementById('reactRoot'));
As I said, this works, however, only for the first level, when I try to recursively loop through for any childNodes, I receive the error:
TypeError: Cannot read property 'fetchMenuSystem' of undefined
I tried to bind the call to the fetch function and bind the mapping, none of which worked.
I would greatly appreciate assistance on this.
OK, I solved this. The problem lied with the routes state & the 'this' keyword.
Here are the changes:
export default class Index extends React.Component {
constructor() {
super();
this.state = {
navItems: [],
routes: []
};
}
................
fetchMenuSystem(data) {
const currRoutesState = this.state.routes;
const self = this;
data.map(function(route) {
................
routePaths.map(function(path) {
................
currRoutesState.push(<Route { ...props } />);
});
if (route.childNodes !== undefined) {
self.fetchMenuSystem(route.childNodes);
}
});
return currRoutesState;
}
................
}
Final thoughts if you plan on using this:
[Re: npm install --save history]
I had to opt for 100% client side routing,
(not isomorphic/universal) so in my imports ....
import createHistory from 'history/lib/createHashHistory';
// import createBrowserHistory from 'history/lib/createBrowserHistory';

Resources