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>
);
}
}
Related
I'm new to Redux, using it with React, and am in need of some help. I have a menu that when a menu item is clicked, another component needs to update some copy. I'm able to dispatch an action and have the store update. However, I can't get the child component (HeroText) to render the new store value in the store.subscribe method when the store values change. Please help and thanks!
import React, { Component } from "react";
import ReactDOM from "react-dom";
import HeroText from "../presentational/HeroText.jsx";
import bgImage from "../../../images/forest_fog.jpg";
import AnantaNavbar from "../presentational/AnantaNavbar.jsx";
import '../../../scss/hero.scss';
import store from '../../store/index';
import { connect } from "react-redux";
const mapStateToProps = state => {
return {
contact: state.contact,
heroText: state.heroText
}
}
class HeroContainer extends Component {
constructor(props)
{
super(props);
this.state = store.getState();
store.subscribe(() => {
console.log(store.getState().heroText);
this.setState({
heroText: store.getState().heroText,
})
})
}
render()
{
return (
<div id="hero-container" style={{backgroundImage: ("url(" + bgImage + ")") || ""}}>
<div className="container">
<HeroText text={this.props.heroText}>
Welcome back {this.props.contact.full_name}
</HeroText>
<AnantaNavbar></AnantaNavbar>
</div>
</div>
);
}
}
export default connect(mapStateToProps)(HeroContainer);
UPDATE
Below is my parent App Container with Provider
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { Navbar, NavbarBrand, NavbarNav, NavbarToggler, Collapse, NavItem, NavLink, Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'react-bootstrap';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import LoginContainer from '../../../js/components/container/LoginContainer.jsx';
import DashboardContainer from '../../../js/components/container/DashboardContainer.jsx';
import HomeContainer from '../../../js/components/container/DashboardContainer.jsx';
import ProfileContainer from '../../../js/components/container/ProfileContainer.jsx';
import HeroContainer from "./HeroContainer.jsx";
import '../../../scss/globals.scss';
import logo from '../../../images/logo1.png';
import { Provider } from 'react-redux';
import store from '../../store/index';
const Router = () => (
<BrowserRouter>
<Switch>
<Route exact path="/" component={LoginContainer} />
<Route exact path="/login" component={LoginContainer} />
<Route exact path="/home" component={HomeContainer} />
<React.Fragment>
<HeroContainer />
<Route path="/dashboard" component={DashboardContainer} />
<Route path="/profile" component={ProfileContainer} />
</React.Fragment>
</Switch>
</BrowserRouter>
);
class AppContainer extends Component {
constructor(props)
{
super(props);
this.state = {
};
}
componentDidMount()
{
}
render()
{
return (
<div>
<Provider store={store}>
<Router></Router>
</Provider>
</div>
);
}
}
export default AppContainer;
The default heroText in the store says "DASHBOARD". When a menu item is clicked, in this case a link to /profile, the heroText should update to "PROFILE" after updating the store.
You can see in the console that the store is changing, but the "DASHBOARD" copy is not reflecting.
RESOLVED
I got this working with the code below. Thanks for all the help!
import React, { Component } from "react";
import ReactDOM from "react-dom";
import HeroText from "../presentational/HeroText.jsx";
import bgImage from "../../../images/forest_fog.jpg";
import AnantaNavbar from "../presentational/AnantaNavbar.jsx";
import '../../../scss/hero.scss';
import store from '../../store/index';
import { connect } from "react-redux";
const mapStateToProps = state => {
return {
contact: state.contact,
heroText: state.heroText
}
}
class HeroContainer extends Component {
constructor(props)
{
super(props);
}
render()
{
return (
<div id="hero-container" style={{backgroundImage: ("url(" + bgImage + ")") || ""}}>
<div className="container">
<HeroText text={store.getState().heroText}>
Welcome back {store.getState().contact.full_name}
</HeroText>
<AnantaNavbar></AnantaNavbar>
</div>
</div>
);
}
}
export default connect(mapStateToProps)(HeroContainer);
Since you are trying to get state from Redux, there's no pointing in keeping it in local state. Plus, you don't need to use store.getState, connect already does that for you.
const mapStateToProps = state => {
return {
contact: state.contact,
heroText: state.heroText
}
}
class HeroContainer extends Component {
render() {
return (
<div id="hero-container" style={{backgroundImage: ("url(" + bgImage + ")") || ""}}>
<div className="container">
<HeroText text={this.props.heroText}>
Welcome back {this.props.contact.full_name}
</HeroText>
<AnantaNavbar></AnantaNavbar>
</div>
</div>
);
}
}
export default connect(mapStateToProps)(HeroContainer);
You also need to make sure that your app is wrapped in a provider, like this:
<Provider store={store}>
<App />
</Provider>
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/>}
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!
I am trying to render a specific component inside of another component based on React, React-Router v4, and Redux in my main 'panel' wrapped in a fixed header and sidebar component.
For example when I select an item from the sidebar, I to render the Detail panel and and load the details based on the id, like: <Route path='/item/:id' component={ItemDetail} />
routes.js
import React, { Component } from 'react';
import { RouteHandler, Switch, Route, DefaultRoute } from 'react-router';
import App from './containers/App';
import Login from './containers/Login';
import LobbyDetail from './components/LobbyDetail';
export default (
<Switch>
<Route exact path="/" component={App} />
<Route exact path="/login" component={Login} />
</Switch>
);
app.js:
import React, { Component } from 'react'
import { Router, Route, Link } from 'react-router'
import { connect } from 'react-redux'
import PropTypes from 'prop-types';
import auth from '../actions/auth';
import Sidebar from '../Components/Sidebar'
class App extends Component {
static propTypes = {
};
/**
*
*/
render() {
const { ... } = this.props
return (
<div className="container-fluid">
<div className="row">
{* I WANT TO RENDER DYNAMIC COMPONENT HERE *}
</div>
<Sidebar currentUser={currentUser}
logout={logout}
/>
</div>
);
}
}
// ...
export default connect(mapStateToProps, mapDispatchToProps)(App)
index.js (basically main app):
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'react-router-redux';
import { createMemoryHistory } from 'history';
import routes from './routes';
import configureStore from './store/store.js';
import { AppContainer } from 'react-hot-loader';
const syncHistoryWithStore = (store, history) => {
const { routing } = store.getState();
if (routing && routing.location) {
history.replace(routing.location);
}
};
const initialState = {};
const routerHistory = createMemoryHistory();
const store = configureStore(initialState, routerHistory);
syncHistoryWithStore(store, routerHistory);
const rootElement = document.querySelector(document.currentScript.getAttribute('data-container'));
const render = () => {
ReactDOM.render(
<AppContainer>
<Provider store={store}>
<ConnectedRouter history={routerHistory}>
{routes}
</ConnectedRouter>
</Provider>
</AppContainer>,
rootElement
);
}
render();
if (module.hot) { module.hot.accept(render); }
What you're looking for is parameterized routing. Make a <Route/> like the following: <Route path='/item/:id' component={ MyComponent } />.
Now in MyComponent you can use the value of props.match.params.id to conditionally render, or if you're trying to load async data based on the value of :id; You can use the componentWillReceiveProps life cycle method and dispatch an action based on the value of this.props.match.params.id.
Note: <Link to='/item/some-item'/> will set the value of match.params.id to 'some-item'.
I'm using the latest ASP.NET 2.0 react SPA template, which is created with the command dotnet new react. It works great out of the box, with hot module replacement (code edits update in browser automatically).
Then I wanted to add a top level app container, called App to hold application state, as described in this blog post:
react without redux article. Should be simple, right? Note that the article uses javascript, but I'm using Typescript, since that's how the template was designed. But after adding a simple top level container component and updating the hot loader config, it doesn't work. I get an error that the component doesn't know how to reload itself. This is the code that is working (from the template):
import './styles/site.scss';
import 'bootstrap';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import { BrowserRouter } from 'react-router-dom';
import * as RoutesModule from './routes';
let routes = RoutesModule.routes;
function renderApp() {
// This code starts up the React app when it runs in a browser. It sets up the routing
// configuration and injects the app into a DOM element.
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href')!;
ReactDOM.render(
<AppContainer>
<BrowserRouter children={ routes } basename={ baseUrl } />
</AppContainer>,
document.getElementById('react-app')
);
}
renderApp();
// Allow Hot Module Replacement
if (module.hot) {
module.hot.accept('./routes', () => {
routes = require<typeof RoutesModule>('./routes').routes;
renderApp();
});
}
And this is the code after my changes, that is NOT working:
import './styles/site.scss';
import 'bootstrap';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import { BrowserRouter } from 'react-router-dom';
import * as RoutesModule from './routes';
import App from './components/App';
import * as AppModule from './components/App'
let routes = RoutesModule.routes;
const render = (Component: any) => {
// This code starts up the React app when it runs in a browser. It sets up the routing
// configuration and injects the app into a DOM element.
//const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href')!;
ReactDOM.render(
<AppContainer>
<Component />
</AppContainer>,
document.getElementById('react-app')
);
}
render(App);
// Allow Hot Module Replacement
if (module.hot) {
module.hot.accept('./components/App', () => {
const nextRoot = (require('./components/App') as typeof AppModule).default;
render(nextRoot);
});
}
For reference, here's the ./routes file (I did not change this file):
import * as React from 'react';
import { Route } from 'react-router-dom';
import { Layout } from './components/Layout';
import { Home } from './components/Home';
import { FetchData } from './components/FetchData';
import { Counter } from './components/Counter';
export const routes = <Layout>
<Route exact path='/' component={ Home } />
<Route path='/counter' component={ Counter } />
<Route path='/fetchdata' component={ FetchData } />
</Layout>;
And here's my new App container component:
import * as React from 'react';
import { RouteComponentProps } from 'react-router';
import { BrowserRouter } from 'react-router-dom';
import * as RoutesModule from '../routes';
import { ReactElement } from "react";
let routes = RoutesModule.routes;
interface AppState {
stateDescription: string;
}
export default class App extends React.Component<{}, {}> {
constructor(props: any) {
super(props);
this.state = { stateDescription: 'My Cool App State' };
}
public render() {
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href')!;
return (
<BrowserRouter children = { routes } basename = { baseUrl } />
);
}
}
Any ideas or suggestions would be much appreciated!
I was able to resolve this by reverting my boot.tsx file to the original template version, and placing my App component in the routes.tsx file instead. You can see I just wrapped everything in the new App element. Here's the new routes.tsx file:
import * as React from 'react';
import { Route } from 'react-router-dom';
import { Layout } from './components/Layout';
import { Home } from './components/Home';
import { FetchData } from './components/FetchData';
import { Counter } from './components/Counter';
import { App } from './components/App';
export const routes = <App>
<Layout>
<Route exact path='/' component={ Home } />
<Route path='/counter' component={ Counter } />
<Route path='/fetchdata' component={ FetchData } />
</Layout>
</App>;
And here's my modified App component that works with this version:
export class App extends React.Component<AppProps, {}> {
constructor(props: AppProps) {
super(props);
this.state = { stateDescription: 'My Cool App State' };
}
public render() {
return (
<div className='appContainer'>
{ this.props.children }
</div>
);
}
}