React class context set sometimes but not all - reactjs

I'm very new to all of this (about three days in), so forgive my ignorance.
I'm using a combination of React, React-Router, and React-Bootstrap. Like this guy, I want to combine React-Router's <Link /> and React-Bootstrap's <NavItem />'s functionality so that the .active class is applied to the <li> element rather (or as well as) the <a> element.
What's really frustrating is I had this working, and then it stopped when I made, what seemed to me, some unrelated changes (that I believe I've undone, since).
Here's my code:
listlink.js
import React from 'react';
import Router from 'react-router';
import classNames from 'classnames';
let Link = Router.Link;
let ListLink = React.createClass({
contextTypes: {
router: React.PropTypes.func
},
/* copied from React-Bootstrap's NavItem.js */
propTypes: {
onSelect: React.PropTypes.func,
active: React.PropTypes.bool,
disabled: React.PropTypes.bool,
href: React.PropTypes.string,
title: React.PropTypes.node,
eventKey: React.PropTypes.any,
target: React.PropTypes.string
},
render: function () {
/* error happens here */
let isActive = this.context.router.isActive(this.props.to);
/* copied from React-Bootstrap's NavItem.js */
let {
disabled,
active,
href,
title,
target,
children,
...props } = this.props;
let classes = {
'active': isActive,
'disabled': disabled
};
return (
<li { ...props } className={ classNames(props.classNames, classes) }>
<Link to={ this.props.to }>{ this.props.children }</Link>
</li>
);
}
});
module.exports = ListLink;
app.js
import React from 'react';
import Router from 'react-router';
import classNames from 'classnames';
import ListLink from '../components/listlink';
import BS from 'react-bootstrap';
/* ... */
let DefaultRoute = Router.DefaultRoute;
let Route = Router.Route;
let RouteHandler = Router.RouteHandler;
let Nav = BS.Nav;
let Navbar = BS.Navbar;
let App = React.createClass({
render: function () {
return (
<div>
<Navbar brand={this.props.toonName} fixedTop>
<Nav bsStyle='tabs'>
<ListLink to="toon">Character</ListLink>
<ListLink to="spell-book">Spell Book</ListLink>
<ListLink to="on-your-turn">On Your Turn</ListLink>
<ListLink to="battle" disabled={true}>Battle</ListLink>
</Nav>
</Navbar>
<RouteHandler />
</div>
);
}
});
let routes = (
<Route name="app" path="/" handler={App} toonName="Ivellios">
<Route name="toon" handler={Toon} />
<Route name="spell-book" handler={SpellBook} />
<Route name="on-your-turn" handler={OnYourTurn} />
<Route name="battle" handler={Battle} />
<DefaultRoute handler={Toon} />
</Route>
);
Router.run(routes, function (Handler) {
React.render(<Handler toonName="Ivellios" />, document.body);
});
module.exports = App;
When I set a breakpoint at let isActive =... in listlink.js, it breaks 5 times, one time for each <ListLink> and then a fifth time where this.props.to is toon again. It's this fifth time where this.context.router is undefined, and referencing .isActive throws an error.
I thought maybe it was because of the <DefaultRoute /> declaration, but that doesn't seem to be the case (commenting the line out doesn't make a difference). Regardless of which /#/[page] is in the URL, it's always toon on the fifth iteration, and it always fails with the same error.
Uncaught TypeError: Cannot read property 'isActive' of undefined
I don't know how to debug this any further. Ideas?

Related

How to dynamically create a React component based on url param?

I'd like my React component to generate based on the url param, in this case, the :id param. I'm struggling rendering the component. I have the different Components defined in separate files (ex. Loader.js, Radio button.js, Accordion menu.js).
Here's my (reduced for clarity) code that is continuously failing :)
import React from 'react';
import { Switch, Link, Route } from 'react-router-dom';
import Grid from '../Components/Grid'
function Overview () {
const components = [
{id: 'accordion-menu',
name: 'Accordion menu'},
{id: 'radio-button',
name: 'Radio button'},
{id: 'loader',
name: 'Loader'},
]
const componentPage = ({match}) => {
const findId = components.find((el) => {
match.params.id = el.id;
return findId.name;
}
)}
return (
<Router>
<div className="components">
<h3>Components</h3>
<p>This header and the menu will always appear on this page!</p>
<menu>
{components.map(({id, name}) => (
<li>
<Link to={`/components/${id}`}>{name}</Link>
</li>
))}
</menu>
<Switch>
<Route exact path={'/components/'} component={Grid}/>
<Route path={'/components/:id'} component={componentPage}/>
</Switch>
</div>
</Router>
)
}
export default Overview;
const componentPage = ({match}) => {
const findId = components.find((el) => {
match.params.id = el.id;
return findId.name;
}
)}
I think what you mean here is to return el.name not findId.name.
the other thing is you are trying to display the function as a component which won't work.

React and i18n - translate by adding the locale in the URL

Hello I'm building a demo application just to learn React and I'm kinda stuck with the translation proccess. What I'm trying to do is have a multi-language website with default language the "Greek" and secondary "English". When Greek are enabled the URL shouldn't contain any locale in the URL but when English are, the URLS should be rewritten with /en/.
i18n Config
import translationEn from './locales/en/translation';
import translationEl from './locales/el/translation';
import Constants from './Utility/Constants';
i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: 'el',
debug: true,
ns: ['translations'],
defaultNS: 'translations',
detection: {
order: ['path'],
lookupFromPathIndex: 0,
},
resources: {
el: {
translations: translationEl
},
en: {
translations: translationEn
}
},
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
}
}, () => {
// Why the fuck this doesnt work automatically with fallbackLng ??
if (!Constants.allowed_locales.includes(i18n.language)) {
i18n.changeLanguage(Constants.default_locale);
}
return;
});
i18n.on('languageChanged', function (lng) {
// if somehow it get injected
if (!Constants.allowed_locales.includes(i18n.language)) {
i18n.changeLanguage(Constants.default_locale);
}
// if the language we switched to is the default language we need to remove the /en from URL
if (Constants.default_locale === lng) {
Constants.allowed_locales.map((item) => {
if (window.location.pathname.includes("/" + item)) {
let newUrl = window.location.pathname.replace("/" + item, "");
window.location.replace(newUrl);
}
})
} else { // Add the /en in the URL
// #todo: add elseif for more than 2 langs because this works only for default + 1 more language
let newUrl = "/" + lng + window.location.pathname;
window.location.replace(newUrl);
}
});
export default i18n;
I used the Language Detector plugin with detection from path so it can parse the locale from URL. Now without the callback function I added at the initialization, the LanguageDetector would set correctly the language if the url was www.example.com/en/ or www.example.com/el or www.example.com/en/company. BUT if I directly accessed the www.example.com/company (before visiting first the home so the locale would be set) i18n would set the locale/language to "company" !!!
There is an option for fallbackLng that I thought that would set the language to what you config it if the LanguageDetector dont detect it, but seems that there isnt an option to set available languages or default language to i18n ( or I'm an idiot and couldnt find it ) so LanguageDetector set whatever he finds in the URL. To fix this I added a Constants file and the callback function above.
Contants.js
const Constants = {
"allowed_locales": ['el','en'],
"default_locale": 'el'
}
export default Constants;
Also I added an event Handler that fires on LanguageChange so it will rewrite the URL with /en/ if English is active or remove the /el/ if Greek is.
index.js
ReactDOM.render(
<BrowserRouter>
<I18nextProvider i18n={i18n}>
<App/>
</I18nextProvider>
</BrowserRouter>
,
document.getElementById('root')
);
App.js
class App extends React.Component {
render() {
return (
<div className="App">
<Suspense fallback="loading">
<Header {...this.props}/>
<Routes {...this.props} />
</Suspense>
</div>
);
}
}
export default withTranslation('translations')(App);
Nothing special for index.js and App.js
Header Component
class Header extends React.Component {
linkGenerator(link) {
// if the current language is the default language dont add the lang prefix
const languageLocale = this.props.i18n.options.fallbackLng[0] === this.props.i18n.language ? null : this.props.i18n.language;
return languageLocale ? "/" + languageLocale + link : link;
}
render() {
return (
<div className="header">
<Navbar bg="light" expand="lg">
<Container>
<Navbar.Brand className="logo" href="/"> <Image src="/assets/logo.png" rounded/>
</Navbar.Brand>
{/*Used For Mobile Navigation*/}
<Navbar.Toggle aria-controls="basic-navbar-nav"/>
<Navbar.Collapse id="basic-navbar-nav" className="float-right">
<Nav className="ml-auto">
<Nav.Link as={NavLink} exact to={this.linkGenerator("/")}>{this.props.t('menu.home')}</Nav.Link>
<Nav.Link as={NavLink} to={this.linkGenerator("/company")}>{this.props.t('menu.company')}</Nav.Link>
</Nav>
<Nav className="mr-auto">
{this.props.i18n.language !== "el" ? <button onClick={() => this.props.i18n.changeLanguage('el')}>gr</button>
: null}
{this.props.i18n.language !== "en" ? <button onClick={() => this.props.i18n.changeLanguage('en')}>en</button>
: null}
</Nav>
</Navbar.Collapse>
</Container>
</Navbar>
</div>
)
}
}
export default Header
In order to create the urls of the Menu with the locale, I created the linkGenerator function
And Finally in my Routes Component which handle all the routing, I added a constant before the actual url so it will work for all of theese /page , /el/page , /en/page
Routes Component
import React from 'react';
import {Switch, Route} from 'react-router-dom';
import CompanyPage from './Pages/CompanyPage';
import HomePage from './Pages/HomePage';
import NotFound from './Pages/NotFound';
class Routes extends React.Component {
render() {
const localesString = "/:locale(el|en)?";
return (
<Switch>
<Route exact path={localesString + "/"} component={HomePage}/>
<Route path={localesString + "/company"} component={CompanyPage}/>
<Route component={NotFound}/>
</Switch>
);
}
}
export default Routes
The code somehow works but is full of hacks like :
Extra config file ( constants.js )
Callback function to change the language from "company" to default locale. ( this triggers 2 page reloads)
functions to handle the locale in the menu and routes
etc..
Isnt there any "build-in" functionality or a better approach in order to achieve the same thing without the above hacks?
I needed the same thing and I found out that you can set the whitelistproperty of i18n.init options and specify the supported languages. After that, if you set checkWhitelist: true inside your detection options, the LanguageDetector will only match the language if it exists on the whitelist array.
Anyway, you still need to define the languageChanged event in order to redirect the page when matching the default language but, you no longer need to redirect if it is another supported language (at least I don't need).
Last thing that I did differently is that I defined the languageChanged event first and only then called the i18n.init, so that it would trigger the event already for the first time that it sets the language.
Here's my code:
i18n.js
import i18n from 'i18next'
import LanguageDetector from 'i18next-browser-languagedetector'
i18n.on('languageChanged', function (lng) {
// if the language we switched to is the default language we need to remove the /en from URL
if (lng === i18n.options.fallbackLng[0]) {
if (window.location.pathname.includes('/' + i18n.options.fallbackLng[0])) {
const newUrl = window.location.pathname.replace('/' + i18n.options.fallbackLng[0], '')
window.location.replace(newUrl)
}
}
})
i18n
.use(LanguageDetector)
.init({
resources: {
en: {
translation: require('./translations/en.js').default
},
pt: {
translation: require('./translations/pt.js').default
}
},
whitelist: ['en', 'pt'],
fallbackLng: ['en'],
detection: {
order: ['path'],
lookupFromPathIndex: 0,
checkWhitelist: true
},
interpolation: {
escapeValue: false,
formatSeparator: '.'
}
})
export default i18n
App.js
import { Route, Switch } from "react-router-dom";
import AboutPage from "./AboutPage";
import HomePage from "./Homepage/HomePage";
import NotFoundPage from "./NotFoundPage";
import PropTypes from "prop-types";
import React from "react";
import { hot } from "react-hot-loader";
import {
Collapse,
Navbar,
NavbarToggler,
NavbarBrand,
Nav,
NavItem,
NavLink } from 'reactstrap';
import i18n from "../i18n";
const baseRouteUrl = "/:locale(pt|en)?";
export const baseUrl = i18n.language === 'en' ? '' : '/'+i18n.language;
class App extends React.Component {
state = {
isOpen: false
}
render() {
return (
<div>
<div>
<Navbar color="grey" expand="md">
<NavbarBrand href="/">Testing</NavbarBrand>
<Nav className="ml-auto" navbar>
<NavItem>
<NavLink href={baseUrl + "/"}>Home</NavLink>
</NavItem>
<NavItem>
<NavLink href={baseUrl + "/about/"}>About</NavLink>
</NavItem>
</Nav>
</Navbar>
</div>
<Switch>
<Route exact path={baseRouteUrl + "/"} component={HomePage} />
<Route path={baseRouteUrl + "/about"} component={AboutPage} />
<Route component={NotFoundPage} />
</Switch>
</div>
);
}
}
App.propTypes = {
children: PropTypes.element
};
export default hot(module)(App);
In my case, when I need to translate something, I import my i18n.js and call the respective key like this:
<div>{i18n.t('home.bannerStart')}</div>
When you define a basename for the Router you don't need to add to every Route.
// in your case use el instead of en
export const baseUrl = () => return (i18n.language === "en" ? "" : "/" + i18n.language);
And the router should be something like this:
<Router basename={baseUrl()}>
...
</Router>
You can call the baseUrl() whenever you need to create a link. Also note that <Link> already takes the basename into regard, so you do not have to define it.
For the language change check my other answer here

Active NavLink to parent element

I'm using React Router v4 and I have a case where on my navigation links, I want to enable the active className to the NavLink parent element, not the NavLink itself.
Is there a way to access the path (match) even though I'm not inside the Switch element?
Or do I have to keep state? Because I'm feeling it's kinda missing the idea of router.
Here's my example, I want to apply the active className to li element not NavLink:
const {
HashRouter,
Switch,
Route,
Link,
NavLink,
} = ReactRouterDOM
const About = () => (
<article>
My name is Moshe and I'm learning React and React Router v4.
</article>
);
const Page = () => (
<Switch>
<Route exact path='/' render={() => <h1>Welcome!</h1>} />
<Route path='/about' component={About}/>
</Switch>
);
const Nav = () => (
<nav>
<ul>
<li><NavLink exact to="/">Home</NavLink></li>
<li><NavLink to="/about">About</NavLink></li>
</ul>
</nav>
);
class App extends React.Component {
render() {
return (
<div>
<Nav />
<Page />
</div>
);
}
}
ReactDOM.render((
<HashRouter>
<App />
</HashRouter>),
document.querySelector("#app"));
https://codepen.io/moshem/pen/ypzmQX
It doesn't seem like it is very easy to achieve. I used withRouter HOC described in react router docs. It gives access to { match, location, history } from props inside components located outside of Routess. In the example I wrapped Nav component to get location and its pathname. Here is the example code:
class Nav extends React.Component {
getNavLinkClass = (path) => {
return this.props.location.pathname === path ? 'active' : '';
}
render() {
return (
<nav>
<ul>
<li className={this.getNavLinkClass("/")}><NavLink exact to="/">Home</NavLink></li>
<li className={this.getNavLinkClass("/about")}><NavLink to="/about">About</NavLink></li>
</ul>
</nav>
)};
}
Nav = withRouter(Nav);
You will probably have to take care of params in your routes (if you have any), to match properly. But you still have to match for each path you have in your NavLink, which might not be pretty code. But the idea is that when the route is changed, Nav is rerendered and correct li is highlighted.
Here is a working example on codesandbox.
Can be achived with Route component
<ul>
<Route path="/about">
{({ match }) => <li className={match ? 'active' : undefined}><Link to="/about">About</Link></li>
</Route>
</ul>
Reference: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/Route.md#children-func
If you abandon the NavLink components altogether, you can create your own components that emulate the "activeness" of a NavLink by using useHistory() and useLocation() from react-router-dom.
Dashboard.js
const routeItems = [
{ route: '/route1', text: 'Route 1' },
{ route: '/route2', text: 'Route 2' },
];
<Router>
<NavBar routeItems={routeItems} />
</Router>
In NavBar.js, we just need to check to see if the current active route is the same as the route for any individual item on the
NavBar.js
import { useHistory, useLocation } from 'react-router-dom';
const NavBar = (props) => {
const { routeItems } = props;
const history = useHistory();
const location = useLocation();
const navItems = routeItems.map((navItem) => {
return (
<div style={{
backgroundColor: navItem.route === location.pathname ? '#ADD8E6' : '',
}}
onClick={() => {
history.push(navItem.route);
}}
>
{navItem.text}
</div>
);
});
return (navItems);
};
export default NavBar;
I found simpler solution for my case I have nested items but I know the base of each nest
for example the base of nest is /customer it contains items like so
/customer/list , /customer/roles ...
So did put some logic in isActive prop in the parent NavLink
code with explanation down :
<NavLink
to={item.route}
activeClassName={classes.activeItem}
onClick={e => handleItemClick(e, key)}
isActive={(match, location) => {
// remove last part of path ( admin/customer/list becomes admin/customer for example )
const pathWithoutLastPart = location.pathname.slice(0, location.pathname.lastIndexOf("/"));
// if current parent is matched and doesn't contain childs activate it
if (item.items.length === 0 && match) {
return true;
}
// if sliced path matches parent path
in case of customer item it becomes true ( admin/customer === admin/customer )
else if (pathWithoutLastPart === item.route) {
return true;
}
// else inactive item
else {
return false;
}
}}
>
...
</NavLink>
Now parent active with his child

React router Link not causing component to update within nested routes

This is driving me crazy. When I try to use React Router's Link within a nested route, the link updates in the browser but the view isn't changing. Yet if I refresh the page to the link, it does. Somehow, the component isn't updating when it should (or at least that's the goal).
Here's what my links look like (prev/next-item are really vars):
<Link to={'/portfolio/previous-item'}>
<button className="button button-xs">Previous</button>
</Link>
<Link to={'/portfolio/next-item'}>
<button className="button button-xs">Next</button>
</Link>
A hacky solution is to manaully call a forceUpate() like:
<Link onClick={this.forceUpdate} to={'/portfolio/next-item'}>
<button className="button button-xs">Next</button>
</Link>
That works, but causes a full page refresh, which I don't want and an error:
ReactComponent.js:85 Uncaught TypeError: Cannot read property 'enqueueForceUpdate' of undefined
I've searched high and low for an answer and the closest I could come is this: https://github.com/reactjs/react-router/issues/880. But it's old and I'm not using the pure render mixin.
Here are my relevant routes:
<Route component={App}>
<Route path='/' component={Home}>
<Route path="/index:hashRoute" component={Home} />
</Route>
<Route path="/portfolio" component={PortfolioDetail} >
<Route path="/portfolio/:slug" component={PortfolioItemDetail} />
</Route>
<Route path="*" component={NoMatch} />
</Route>
For whatever reason, calling Link is not causing the component to remount which needs to happen in order to fetch the content for the new view. It does call componentDidUpdate, and I'm sure I could check for a url slug change and then trigger my ajax call/view update there, but it seems like this shouldn't be needed.
EDIT (more of the relevant code):
PortfolioDetail.js
import React, {Component} from 'react';
import { browserHistory } from 'react-router'
import {connect} from 'react-redux';
import Loader from '../components/common/loader';
import PortfolioItemDetail from '../components/portfolio-detail/portfolioItemDetail';
import * as portfolioActions from '../actions/portfolio';
export default class PortfolioDetail extends Component {
static readyOnActions(dispatch, params) {
// this action fires when rendering on the server then again with each componentDidMount.
// but not firing with Link...
return Promise.all([
dispatch(portfolioActions.fetchPortfolioDetailIfNeeded(params.slug))
]);
}
componentDidMount() {
// react-router Link is not causing this event to fire
const {dispatch, params} = this.props;
PortfolioDetail.readyOnActions(dispatch, params);
}
componentWillUnmount() {
// react-router Link is not causing this event to fire
this.props.dispatch(portfolioActions.resetPortfolioDetail());
}
renderPortfolioItemDetail(browserHistory) {
const {DetailReadyState, item} = this.props.portfolio;
if (DetailReadyState === 'WORK_DETAIL_FETCHING') {
return <Loader />;
} else if (DetailReadyState === 'WORK_DETAIL_FETCHED') {
return <PortfolioItemDetail />; // used to have this as this.props.children when the route was nested
} else if (DetailReadyState === 'WORK_DETAIL_FETCH_FAILED') {
browserHistory.push('/not-found');
}
}
render() {
return (
<div id="interior-page">
{this.renderPortfolioItemDetail(browserHistory)}
</div>
);
}
}
function mapStateToProps(state) {
return {
portfolio: state.portfolio
};
}
function mapDispatchToProps(dispatch) {
return {
dispatch: dispatch
}
}
export default connect(mapStateToProps, mapDispatchToProps)(PortfolioDetail);
PortfolioItemDetail.js
import React, {Component} from 'react';
import {connect} from 'react-redux';
import Gallery from './gallery';
export default class PortfolioItemDetail extends React.Component {
makeGallery(gallery) {
if (gallery) {
return gallery
.split('|')
.map((image, i) => {
return <li key={i}><img src={'/images/portfolio/' + image} alt="" /></li>
})
}
}
render() {
const { item } = this.props.portfolio;
return (
<div className="portfolio-detail container-fluid">
<Gallery
makeGallery={this.makeGallery.bind(this)}
item={item}
/>
</div>
);
}
}
function mapStateToProps(state) {
return {
portfolio: state.portfolio
};
}
export default connect(mapStateToProps)(PortfolioItemDetail);
gallery.js
import React, { Component } from 'react';
import { Link } from 'react-router';
const Gallery = (props) => {
const {gallery, prev, next} = props.item;
const prevButton = prev ? <Link to={'/portfolio/' + prev}><button className="button button-xs">Previous</button></Link> : '';
const nextButton = next ? <Link to={'/portfolio/' + next}><button className="button button-xs">Next</button></Link> : '';
return (
<div>
<ul className="gallery">
{props.makeGallery(gallery)}
</ul>
<div className="next-prev-btns">
{prevButton}
{nextButton}
</div>
</div>
);
};
export default Gallery;
New routes, based on Anoop's suggestion:
<Route component={App}>
<Route path='/' component={Home}>
<Route path="/index:hashRoute" component={Home} />
</Route>
<Route path="/portfolio/:slug" component={PortfolioDetail} />
<Route path="*" component={NoMatch} />
</Route>
Could not get to the bottom of this, but I was able to achieve my goals with ComponentWillRecieveProps:
componentWillReceiveProps(nextProps){
if (nextProps.params.slug !== this.props.params.slug) {
const {dispatch, params} = nextProps;
PortfolioDetail.readyOnActions(dispatch, params, true);
}
}
In other words, for whatever reason when I use React Router Link to link to a page with the SAME PARENT COMPONENT, it doesn't fire componentWillUnMount/componentWillMount. So I'm having to manually trigger my actions. It does work as I expect whenever I link to Routes with a different parent component.
Maybe this is as designed, but it doesn't seem right and isn't intuitive. I've noticed that there are many similar questions on Stackoverflow about Link changing the url but not updating the page so I'm not the only one. If anyone has any insight on this I would still love to hear it!
It's good to share the components code also. However, I tried to recreate the same locally and is working fine for me. Below is the sample code,
import { Route, Link } from 'react-router';
import React from 'react';
import App from '../components/App';
const Home = ({ children }) => (
<div>
Hello There Team!!!
{children}
</div>
);
const PortfolioDetail = () => (
<div>
<Link to={'/portfolio/previous-item'}>
<button className="button button-xs">Previous</button>
</Link>
<Link to={'/portfolio/next-item'}>
<button className="button button-xs">Next</button>
</Link>
</div>
);
const PortfolioItemDetail = () => (
<div>PortfolioItemDetail</div>
);
const NoMatch = () => (
<div>404</div>
);
module.exports = (
<Route path="/" component={Home}>
<Route path='/' component={Home}>
<Route path="/index:hashRoute" component={Home} />
</Route>
<Route path="/portfolio" component={PortfolioDetail} />
<Route path="/portfolio/:slug" component={PortfolioItemDetail} />
<Route path="*" component={NoMatch} />
</Route>
);
componentWillReceiveProps is the answer to this one, but it's a little annoying. I wrote a BaseController "concept" which sets a state action on route changes EVEN though the route's component is the same. So imagine your routes look like this:
<Route path="test" name="test" component={TestController} />
<Route path="test/edit(/:id)" name="test" component={TestController} />
<Route path="test/anything" name="test" component={TestController} />
So then a BaseController would check the route update:
import React from "react";
/**
* conceptual experiment
* to adapt a controller/action sort of approach
*/
export default class BaseController extends React.Component {
/**
* setState function as a call back to be set from
* every inheriting instance
*
* #param setStateCallback
*/
init(setStateCallback) {
this.setStateCall = setStateCallback
this.setStateCall({action: this.getActionFromPath(this.props.location.pathname)})
}
componentWillReceiveProps(nextProps) {
if (nextProps.location.pathname != this.props.location.pathname) {
this.setStateCall({action: this.getActionFromPath(nextProps.location.pathname)})
}
}
getActionFromPath(path) {
let split = path.split('/')
if(split.length == 3 && split[2].length > 0) {
return split[2]
} else {
return 'index'
}
}
render() {
return null
}
}
You can then inherit from that one:
import React from "react";
import BaseController from './BaseController'
export default class TestController extends BaseController {
componentWillMount() {
/**
* convention is to call init to
* pass the setState function
*/
this.init(this.setState)
}
componentDidUpdate(){
/**
* state change due to route change
*/
console.log(this.state)
}
getContent(){
switch(this.state.action) {
case 'index':
return <span> Index action </span>
case 'anything':
return <span>Anything action route</span>
case 'edit':
return <span>Edit action route</span>
default:
return <span>404 I guess</span>
}
}
render() {
return (<div>
<h1>Test page</h1>
<p>
{this.getContent()}
</p>
</div>)
}
}
I got stuck on this also in React 16.
My solution was as follows:
componentWillMount() {
const { id } = this.props.match.params;
this.props.fetchCategory(id); // Fetch data and set state
}
componentWillReceiveProps(nextProps) {
const { id } = nextProps.match.params;
const { category } = nextProps;
if(!category) {
this.props.fetchCategory(id); // Fetch data and set state
}
}
I am using redux to manage state but the concept is the same I think.
Set the state as per normal on the WillMount method and when the WillReceiveProps is called you can check if the state has been updated if it hasn't you can recall the method that sets your state, this should re-render your component.
I am uncertain whether it fixes the original problem, but I had a similar issue which was resolved by passing in the function callback () => this.forceUpdate() instead of this.forceUpdate.
Since no one else is mentioning it, I see that you are using onClick={this.forceUpdate}, and would try onClick={() => this.forceUpdate()}.
Try to import BrowserRouter instead of Router
import { Switch, Route, BrowserRouter as Router } from 'react-router-dom;
It worked for me after spending a couple of hours solving this issue.
I solved this by building '' custom component instead of '', and inside it I use in the method instead of :
import * as React from "react";
import {Navigate} from "react-router-dom";
import {useState} from "react";
export function ReactLink(props) {
const [navigate, setNavigate] = useState(<span/>);
return (
<div style={{cursor: "pointer"}}
onClick={() => setNavigate(<Navigate to={props.to}/>)}>
{navigate}
{props.children}
</div>
}

React JS Error after requiring ReactRouter - Warning: React.createElement: type should not be null, undefined, boolean, or number

I'm trying to learn ReactJS at the moment, with all the differing tutorials using ES5 / ES6 and all the other differences in versions of React it is a little frustrating to say the least. Can anyone see why the code below would throw the following error to console?
Error Message:
Warning: React.createElement: type should not be null, undefined, boolean, or number. It should be a string (for DOM elements) or a ReactClass (for composite components)
Uncaught Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, Navigation } from 'react-router'
/*
Main App layout
*/
var App = React.createClass ({
render: function () {
return (
<div className="catch-of-the-day">
<div className="menu">
<Header tagline="Seafood and Eat it!"/>
</div>
<Order/>
<Inventory/>
</div>
)
}
});
/* Header */
var Header = React.createClass ({
render: function() {
return (
<header className="top">
<h1>Catch
<span className="ofThe">
<span className="of">of</span>
<span className="the">the</span>
</span>
day</h1>
<h3><span>{this.props.tagline}</span></h3>
</header>
)
}
});
/* Order */
var Order = React.createClass ({
render: function() {
return (
<p>Order</p>
)
}
});
/* <Inventory/> */
var Inventory = React.createClass ({
render: function() {
return (
<p>Inventory</p>
)
}
});
/*
Store Picker
This will let us make <StorePicker/>
*/
var StorePicker = React.createClass ({
render: function () {
return (
<form className="store-selector">
<h2>Please enter a store</h2>
<input type="text" ref="storeId" />
<input type="submit" />
</form>
)
}
});
/*
Routes
*/
var Routes = (
<Router>
<Route path="/" component={StorePicker} />
<Route path="/store/:storeid" component={App} />
</Router>
)
ReactDOM.render(<Routes/>, document.querySelector('#main'));
Try this:
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, Navigation } from 'react-router'
ReactDOM.render(
<Router>
<Route path="/" component={StorePicker} />
<Route path="/store/:storeid" component={App} />
</Router>,
document.querySelector('#main'));
The code you show us does not include how your are importing your components StorePicker and App, but I can tell you that by writing
component="{App}"
You are passing a string as a parameter to the Route component, to pass your component, you should write:
<Route path="/store/:storeid" component={App} />
Hope this helps you!
Ok the problem lay with my Router component, I was not actually rendering it properly, correct code below:
/*
Routes
*/
var Routes = React.createClass({
render: function() {
return (
);
}
});
ReactDOM.render(, document.querySelector('#main'));

Resources