Getting TypeError with React Router - reactjs

I'm trying to implement the latest version of React Router (v.4.3.1) in Material UI Tabs, but getting the following error: TypeError: Cannot read property 'location' of undefined. I believe I need to use some sort of location history, but not sure of the implementation given the updates to React Router.
UPDATE
I've managed to fix the location of undefined issue and get the navbar and content to show by using BrowseRouter as Router, but now the navbar links are not showing in the navbar. There isn't any logic to show or hide the links, so not sure whats causing them to not show up.
App.js
import React, { Component } from "react";
import Main from './Main';
import { Router, Route, Switch, Link } from 'react-router-dom';
class App extends Component {
constructor() {
super();
this.state = {
appTitle: "App"
};
}
componentDidMount() {
document.title = this.state.appTitle;
}
render() {
return (
<ThemeProvider theme={lightTheme}>
<Router>
<Switch>
<Route component={Main} exact path="/" />
</Switch>
</Router>
</ThemeProvider>
);
}
}
export default App;
Main.js
import React from 'react';
import PropTypes from 'prop-types';
import { NavBar } from "./Components/Navbar/";
const GetNavBar = props => {
return (<NavBar appTitle={props.appTitle} />);
}
const Main = props => {
return (
<div className={props.classes.root}>
<GetNavBar appTitle={props.appTitle} { ...data.appHeader } />
<GetPageComponents {...props} data={data}/>
</div>
)};
Main.propTypes = {
onClose: PropTypes.func
};
export default withStyles(styles)(Main);
Navbar.js
import React from "react";
import { Tabs, Tab } from 'tabs';
import PropTypes from 'prop-types';
import { Router } from 'react-router-dom';
const TabLink = linkProps => (
<a {...linkProps}>{linkProps.linklabel}</a>
);
function renderTab({ getTabProps }) {
const { className, label, ...rest } = getTabProps();
return (
<Tab
className={`${className}`}
component={TabLink}
linklabel={label}
to={TabLink}
{...rest}
/>
);
}
renderTab.propTypes = {
getTabProps: PropTypes.func
};
const NavBar = ({onChange, onDeselect, classes}, props) => {
return (
<div className={styles.headerContainer}>
<AppHeader
data-testid="app-header-default-example"
position="static"
className={styles.root}
getTabProps={({ getTabProps }) => (
<div {...getTabProps({})}>
<Router>
<Tabs>
<Tab label="Home" component={renderTab} exact to="/" />
<p className={styles.headerAppTitle}>{props.appTitle} .
</p>
</Tabs>
</Router>
</div>
)}
/>
</div>
)};
NavBar.propTypes = {
onChange: PropTypes.func,
onDeselect: PropTypes.func
};
export default withStyles(styles)(NavBar);

With React Router 4.x, try changing your import to something like the following to import a higher-level router such as BrowserRouter. This example is using an alias Router to refer to the named import BrowserRouter, but it's not necessary, you could just use BrowserRouter without the alias:
import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';
Technically you can import Router, but that would be from react-router, not react-router-dom and you would usually separately create a history object for it:
import { Router } from 'react-router';
That being said, you are most likely looking for BrowserRouter from react-router-dom, but there are other options such as HashRouter.
Updated
Regarding the NavBar component. You are also trying to import Router from react-router-dom and use Router in it's markup. First you would need to remove that import and Router, as it's not needed. If the goal is to use material-ui Tabs/Tab as navigation, you would need to use Link components instead of <a> elements for the TabLink component.
import { Link } from 'react-router-dom';
// ...
const TabLink = linkProps => (
<Link {...linkProps}>{linkProps.linklabel}</Link>
);
Here is a StackBlitz demonstrating the functionality at a basic level.
Hopefully that helps!

Related

Link of react-router-dom updates the URL without rendering the component

I'm using the react-router-dom Link component to manage my navigation but it doesn't work.
The Link changes the URL but doesn't render the component.
Here is a simple app describing my problem. I'm trying to use My App header as go home link:
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import React from 'react';
const Navigation = () => {
return (
<div>
<Link to="/events">
<h1>My Application</h1>
</Link>
</div>
);
};
const ConnectedNavigation = connect((state) => state)(Navigation);
export default ConnectedNavigation;
App.jsx code :
import React from 'react';
import { Provider } from 'react-redux';
import { store } from '../store/index';
import ConnectedDashboard from './Dashboard';
import ConnectedEventDetails from './EventDetails';
import { Router, Route } from 'react-router-dom';
import { history } from '../store/history';
import ConnectedNavigation from './Navigation';
export const Main = () => (
<Router history={history}>
<Provider store={store}>
<div>
<ConnectedNavigation />
<Route exact path="/events" render={() => <ConnectedDashboard />} />
<Route
exact
path="/event/:id"
//match prop is necessary in order to determine which event
//we are looking for
render={({ match }) => <ConnectedEventDetails match={match} />}
/>
</div>
</Provider>
</Router>
);
As you can see i added exact on my Routes to avoid any confusions
This problem comes from this line:
import { Router, Route } from 'react-router-dom';
The correct import is :
import { BrowserRouter as Router, Route } from 'react-router-dom';

Navigate programmatically in React router 4 with mix of function/class components & TypeScript

I'm using React Router 4 in a TypeScript app where I have a React.Component that's used within a React.FunctionalComponent. I need to be able to navigate programmatically to a particular route from within the React.Component, but I can't seem to figure out how to pass the router down to the child component so that I can call this.props.history.push(). What complicates matters is that I'm using TypeScript, too.
Here's a code sandbox with a working demo of my component layout: https://codesandbox.io/s/react-programmatic-routing-xebpg
And now, the components:
app.tsx:
import * as React from 'react';
import { HashRouter } from 'react-router-dom';
import Header from './header';
import Footer from './footer';
import AppRouter from './app-router';
export default class App extends React.PureComponent {
public render() {
return (
<HashRouter>
<Header />
<AppRouter />
<Footer />
</HashRouter>
);
}
}
header.tsx:
import * as React from 'react';
import Navbar from 'react-bootstrap/Navbar';
import Nav from 'react-bootstrap/Nav';
import { NavLink } from 'react-router-dom';
export default class Header extends React.PureComponent<any> {
public render() {
return (
<Navbar>
<Nav.Link as={NavLink} exact to="/home">
Home
</Nav.Link>{' '}
<Nav.Link as={NavLink} to="/customers">
Customers
</Nav.Link>
</Navbar>
);
}
}
app-router.tsx:
import * as React from 'react';
import { Switch, Route } from 'react-router-dom';
import Home from './pages/home';
import Customers from './pages/customers';
const AppRouter: React.FC = () => {
return (
<div>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/home" component={Home} />
<Route path="/customers" component={Customers} />
</Switch>
</div>
);
};
export default AppRouter;
pages/customers.tsx:
import * as React from 'react';
import MyFakeGrid from './customers-grid';
const Customers: React.FC = () => {
return (
<div>
<p>This is the customers page</p>
<MyFakeGrid />
</div>
);
};
export default Customers;
pages/customers-grid.tsx:
import * as React from 'react';
import { NavLink } from 'react-router-dom';
export default class MyFakeGrid extends React.Component {
public render() {
return (
<div style={{ borderColor: 'lightgray', borderStyle: 'solid' }}>
<p>
I need to be able to route programmatically from this
component
</p>
<p>
but I can't just use a NavLink like 'Home' (below), I have
to be able to navigate from within a method
</p>
<NavLink to="/home">Home</NavLink>
</div>
);
}
}
pages/home.tsx:
import * as React from 'react';
const Home: React.FC = () => {
return (
<div>
<p>This is the home page</p>
</div>
);
};
export default Home;
I've recently started learning React and I don't want to re-write my class-based components as functional components, which have become quite detailed/useful, especially not given React's gradual adoption strategy.
Base on React-router training, You can get access to the history object's properties and the closest 's match via the withRouter higher-order component. withRouter will pass updated match, location, and history props to the wrapped component whenever it renders.
For example, you can re-write Customer component as blow:
import * as React from 'react';
import MyFakeGrid from './customers-grid';
import { withRouter } from "react-router";
const Customers: React.FC = () => {
return (
<div>
<p>This is the customers page</p>
<MyFakeGrid />
</div>
);
};
export default withRouter(Customers);
now you access to the history and other parameter as i said, and you can easily navigate between routes.

How can I hide my navbar on the homepage and make it only visible once the user has logged in?

I'm making a react app, I made a navbar and It renders in all of the components and I only want it visible in one, I made a HOC function but It still doesnt work correctly.
Higher Order Components
this is my navigation component
import React from 'react';
import {NavLink} from "react-router-dom";
const Navigation = () => {
return (
<div id = "navlinks">
<NavLink to = "">PROMOS</NavLink>
<NavLink to = "" >Nueva Orden</NavLink>
<NavLink to = "" >Ordenes</NavLink>
<NavLink to = "">Menú</NavLink>
<NavLink id = "logout" to = "/" >Cerrar Sesión</NavLink>
</div>
)
}
export default Navigation;
and this is is my router
import React, { Component } from 'react';
import { BrowserRouter , Route} from "react-router-dom";
import './App.css';
import Home from "./components/Home";
import Menu from "./components/Menu";
import Navigation from "./components/Navigation";
class App extends Component {
render() {
return (
<BrowserRouter>
<div>
<Navigation/>
<div>
<Route path= "/" component = {Home} exact />
<Route path= "/Menu" component = {Menu}/>
</div>
</div>
</BrowserRouter>
);
}
}
export default App;
and my HOC component
import React, { Component } from 'react';
const LoaderHOC = (WrappedComponent) => {
return class LoaderHOC extends Component{
render(){
this.props.Navigation.length === 0 ? <div className = 'Loader'></div> : <WrapperComponent {... this.props}/>
}
}
}
export default LoaderHOC;
I suppose you have a way to determine whether your user is loggedIn or not. Suppose, you have store the information in isLoggedIn variable, than you can do following to hide navigation if user is not logged in,
{ isLoggedIn && <Navigation /> }
But once your application grows, I suggest you to make different routes depending on the public/private state.
Create a PrivateRoute.js file
import React, { Component } from 'react';
import { Redirect, Route } from 'react-router-dom';
import Navigation from "./components/Navigation";
class PrivateRoute extends Component {
render() {
// use your own logic here
const isLoggedIn = !!localStorage.getItem('token');
if (!isLoggedIn) {
return <Redirect to='/' />;
}
return (
<div>
<Navigation />
// your private route
</div>
}
}
export default PrivateRoute;
create your PublicRoute.js file
import React, { Component } from 'react';
import { Redirect, Route } from 'react-router-dom';
class PublicRoute extends Component {
render() {
<div>
// your all public route
</div>
}
}
export default PublicRoute;
Now Just include those into your main file
import React, { Component } from 'react';
import { BrowserRouter , Route} from "react-router-dom";
import { PublicRoute, PrivateRoute } from './routes';
import './App.css';
import Home from "./components/Home";
import Menu from "./components/Menu";
class App extends Component {
render() {
return (
<BrowserRouter>
<div>
<PublicRoute />
<PrivateRoute />
</div>
</BrowserRouter>
);
}
}
export default App;
Don't use HOC for this.
You must have store somewhere that user is loggedIn, if not I would suggest you to use a localStorage like,
localStorage.setItem("loggedIn", true);
Note: To setup a localStorage you don't need any extra configuration.
In your router you can use this to hide your component,
{localStorage.getItem("loggedIn") && <Navigation/>}

Reactjs: returning children with cloneElement

I'm new to this and following a tutorial. I've actually copied the code from the starter files but the children do now show in the React devtools, hence not rendering. The components are fine and I can render them individually. PhotoGrid doesn't show as a child of Main that's all. This is the code:
Main.js
import React from 'react';
import { Link } from 'react-router';
const Main = React.createClass({
render() {
const props = this.props;
return (
<div>
<h1>
<Link to="/">Reduxstagram</Link>
</h1>
{ React.cloneElement(props.children, props) }
</div>
);
}
});
export default Main;
App.js
import React from 'react';
import { render } from 'react-dom';
// Import css
import css from './styles/style.styl';
// Import Components
import Main from './components/Main';
import Single from './components/Single';
import PhotoGrid from './components/PhotoGrid';
// import react router deps
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
render(
<Router history={browserHistory}>
<Route path="/" component={Main}>
<IndexRoute component={PhotoGrid}></IndexRoute>
<Route path="/view/:postId" component={Single}></Route>
</Route>
</Router>, document.getElementById('root'));
{ props.children } will render the children of the component (from your question I see no need to use React.cloneElement). See modified code below:
const Main = React.createClass({
render() {
const props = this.props;
return (
<div>
<h1>
<Link to="/">Reduxstagram</Link>
</h1>
{ props.children }
</div>
);
}
});
Sidenote
React-router has recently been updated to v4. You may want to use the new router (docs for react-router-4).

react-leaflet's Popup contains a react-route's Link

My app uses a react-leaflet for generating a Map with Markers and Popups. And I need to give a link to the other page from Popup by <Link/> component from react-router.
/* app.js */
import React from 'react';
import { render } from 'react-dom';
import { Router, Route, browserHistory } from 'react-router';
import App from './components/App';
import Map from './components/Map';
const Root = () =>
<Router history={browserHistory}>
<Route path='/' component={App}>
<Route path='map' component={Map} />
</Route>
<Router>
render(<Root />, document.getElementById('root'));
/* components/Map/index.js */
import React from 'react';
import { Router, Route, browserHistory } from 'react-router';
import App from './components/App';
import Map from './components/Map';
const Map = () =>
<Map>
<Marker position={[10, 10]}>
<Popup>
<div>
<Link to="/">Main page</Link>
</div>
</Popup>
</Marker>
<Map>
export default Map;
But passing through the link I get an error:
<Link>s rendered outside of a router context cannot navigate.
It is because the content of opened Popup is removed from router context and is placed below.
I suppose, that I can to put router.push() into Popup. But maybe is it possible to use a <Link/>?
Thanks!
So, I created ContextProvider component-creator:
import React, { PureComponent, PropTypes } from 'react';
export default function createContextProvider(context) {
class ContextProvider extends PureComponent {
static propTypes = {
children: PropTypes.node,
};
static childContextTypes = {};
getChildContext() {
return context;
}
render() {
return this.props.children;
}
}
Object.keys(context).forEach((key) => {
ContextProvider.childContextTypes[key] = PropTypes.any.isRequired;
});
return ContextProvider;
}
And used it in the creation of maps marker:
import React, { PureComponent, PropTypes } from 'react';
import { Marker, Popup } from 'react-leaflet';
import createContextProvider from '../ContextProvider';
export default class SomeComponent extends PureComponent {
static contextTypes = {
router: PropTypes.object,
// Place here any other parameters from context
};
render() {
const { position, children, ...props } = this.props;
const ContextProvider = createContextProvider(this.context);
return (
<Marker {...props} position={position}>
<Popup>
<ContextProvider>
<div>
{children}
</div>
</ContextProvider>
</Popup>
</Marker>
);
}
}

Resources