React Router Link doesn't work with LeafletJS - javascript

Versions:
react-router-dom 4.1.1
react-router-redux 5.0.0-alpha.4
react-leaflet 1.1.3
leaflet 1.0.3
Steps to reproduce
I create a leaflet map. In which I add some markers. These markers have popups.
In each of these popup I want to have a <Link>
Also if it helps this is my Routing config:
ReactDOM.render(
<Provider store={store}>
<div>
<AppContainer />
<ConnectedRouter history={history}>
<div>
<MenuContainer />
<Switch>
<Route path='/:area/:sport/list' component={ListContainer} />
<Route path='/:area/:sport/map' component={MapContainer} />
<Route path='/:area/:sport/rasp' component={RaspContainer} />
<Route path='/:shortcode/details' component={StationDetailsContainer} />
<Redirect exact from='/' to='/wellington/paragliding/list' />
<Route component={NoMatch} />
</Switch>
</div>
</ConnectedRouter>
</div>
</Provider>,
document.getElementById('root')
)
Expected Behavior
I can see my link and click on it when popup opens.
Actual Behavior
Impossible to see the link. It's not generated.
Extra details
Inside my <MapMode> I use <Map> from leaflet.
If I set a <Link> just above the <Map> tag it works.
As soon as I want to have a link inside my <Map>, somehow it breaks.
This is the React structure of my page, <Popup> tag just contains null as Javascript is breaking:
It's quite a complex problem so feel free to ask me questions.
Thanks.

I tried the solution suggested by Tharaka but it didn't work for me. It looks like react-leaflet's Popup is using it's own context, thus blocking context that is passed from higher levels. However, inspired by this solution I came up with another one, really simple & based on the composition principal.
I created RouterForwarder component
import React, { Component } from 'react'
import PropTypes from 'prop-types'
class RouterForwarder extends Component {
getChildContext() {
return this.props.context
}
render() {
return <span>{this.props.children}</span>
}
}
RouterForwarder.childContextTypes = {
router: PropTypes.object.isRequired,
}
RouterForwarder.propTypes = {
context: PropTypes.object.isRequired,
}
export default RouterForwarder
and then used it in my component (the one that renders Map, Marker, Popup & Link) in the following way:
import RouterForwarder from './RouterForwarder'
class MyComponent extends Component {
render() {
return (
...
<Popup>
<RouterForwarder context={this.context}>
<Link to={'my destination'}>Go to My Destination</Link>
</RouterForwarder>
</Popup>
...)
}
}
MyComponent.contextTypes = {
router: PropTypes.object,
}

I'm not 100% sure about this answer. But anyway I'm going to try because I think at least it might shed some light to anyone who will try to solve this problem in future.
I got the first hint from this issue in react-leaflet GitHub repo. According to that and your error, it seems the problem is Popup can't access the router from the context because context isn't passed into the Popup with the way they render it. So we should be able to fix the problem if we can explicitly pass the context to Popup.
Then I found a way to explicitly pass the context into a component in this StackOverflow answer. With that, I think you should be able to use a HoC(Higher order Component) as follows to solve your problem.
This is the HoC that inject context to a component:
function withContext(WrappedComponent, context){
class ContextProvider extends React.Component {
getChildContext() {
return context;
}
render() {
return <WrappedComponent {...this.props} />
}
}
ContextProvider.childContextTypes = {};
Object.keys(context).forEach(key => {
ContextProvider.childContextTypes[key] = React.PropTypes.any.isRequired;
});
return ContextProvider;
}
Let's say you are using Popup inside a component called MapMaker. Then you can inject the context with router into Popup using the HoC like this.
class MapMaker extends React.Component {
//......
// This make sure you have router in you this.context
// Also you can add any other context that you need to pass into Popup
static contextTypes = {
router: React.PropTypes.object.isRequired
}
render(){
const PopupWithContext = withContext(Popup, this.context);
return (
//..... your JSX before Popup
<PopupWithContext/> // with your props
//..... your JSX after Popup
);
}
}

My solution (it's a workaround but works great and I see no downsides):
I solved by using (in react router v3 with redux)
<a onClick={() => goTo(params)} />
whereas goTo is defined in
const mapDispatchToProps = (dispatch) => bindActionCreators({
goTo(id) {
return push(`/<url>/${id}`); // push from 'react-router-redux'
},
}, dispatch);

Related

How to declare type for functional React component using object destructuring in props?

I am still learning TypeScript and declaring (advanced) types. I am trying to convert my CRA project to typescript and I have following component in it. How I should declare the types for this?
This component is almost 1:1 from react-router-dom examples, but I couldn't find any example showing this written in TypeScript
// ProtectedContent.tsx
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import Auth from '../services/Auth';
const ProtectedContent = ({ component: Component, ...rest }) => {
return (
<Route {...rest} render={(props) => {
if (Auth.isAuthenticated()) {
return (
<Component {...props} />
);
} else {
return (
<Redirect to={{
pathname: '/auth/signin',
state: { from: props.location },
}} />
);
}
}} />
);
};
export default ProtectedContent;
I tried creating an interface for the parameters:
interface PCProps extends HTMLAttributes<HTMLElement>{
component: React.Component;
}
const ProtectedContent = ({ component: Component, ...rest }: PCProps) => {
// .......
}
But then I was having problem with the line
return (
<Component {...props} />
);
as there was error saying
JSX element type 'Component' does not have any construct or call signatures. ts(2604)
Then when I was trying to fix that I ended going down an insane rabbit hole and lost track and understanding of what was going on anymore.
So how should I declare the types for this so that the component could be any React component and then destructure rest of the props neatly?
TS version is 3.8.3
Edit: (Possible solution)
From the depths of internet I found this "solution". I can declare the component in the PCProps interface as 'ReactType' and the use it like this:
interface PCProps {
component: React.ReactType;
}
const ProtectedContent = ({ component: Component, ...rest }): PCProps => {
...
}
This will get rid of the error, but why? What is this 'ReactType'? I went through the docs and found nothing related to it. Is there anyone experienced in React and TS who could explain this and also how this kind of component should be typed?
I don't really like to use this solution, because I have no idea what's happening.
Solution source
Edit2:
Sandbox showing original, proposed solution using React.FC and possible solution using React.ReactType. Check all the three files in this sandbox.
Sandbox for this question
Make sure your file extension is .tsx and not .ts and that you're declaring component: React.FC;. Working example: codesandbox.io/s/silly-hamilton-1xuyu?file=/src/App.tsx:95-97

How to call a function from navigationbar in React native?

I am trying to create a screen where clicking on the title calls a function.
The below code gives the error "undefined is not a function (evaluating _this2._test()"
When I define _test outside of the class Home, I can call the function (but _test can no longer access this.props).
Also, I am able to call the function _test from render() by using this._test.
It looks like this might be an issue with "this", but I can't figure out what the problem really is. Can someone please help?
#connect(data => Home.getDataProps(data))
export default class Home extends React.Component {
static route = {
navigationBar: {
title: <Text onPress={()=>{this._test()}}>Home</Text>
}
}
_test = () => {
console.log("TEST SUCCESS")
console.log(this.props)
}
...
Thats because your trying to call method from static object (more about static methods on this page: MDN static ) which belongs to the class itself, not to the instance of the class. Maybe you should change test() method to be static also. You can't call non-static method from static method, however you can do opposite, call static from instance.
Moreover, the <Text /> component is not being rendered inside your <Home /> component. Thats why its a static object. And thats why you don't have access to <Home /> component method by this operator. This operator point to the route object, not your component. Its just a configuration to list components that needs to be rendered.
You can establish component to component communication using for example redux actions.
I'm not sure what you are trying to accomplish, but it would be good idea to use your own component instead of <Text /> which can dispatch redux action while pressing the <Text /> component. For example:
...
import React, { Component } from 'react'
import { connect } from 'react-redux'
import onTextPress from './redux/actions'
...
class MyText extends Component {
render() {
return (
<Text onPress={() => this.props.onTextPress()} />
)
}
}
export default connect(null, { onTextPress })(MyText)
and of course connect your <Home /> component to redux to handle changes occurred in redux store.
EDIT:
In your home component you have to modify static route object to use your new, custom component:
...
import MyText from './components/MyText'
...
export default class Home extends React.Component {
static route = {
navigationBar: {
title: <MyText />
}
}
...
This will allow you to dispatch any action (in my example onTextPress action) from within navigation bar.

React-router v4 this.props.history.push(...) not working

I'm trying to route programatically using this.props.history.push(..) but it doesn't seem to work.
Here's the router:
import {
BrowserRouter as Router,
Route
} from 'react-router-dom';
<Router>
<Route path="/customers/" exact component={CustomersList} />
<Route path="/customers/:id" exact component="{Customer} />
</Router>
In CustomerList, a list of customers is rendered. Clicking on a customer (li) should make the application route to Customer:
import { withRouter } from 'react-router'
class Customers extends Component {
static propTypes = {
history: PropTypes.object.isRequired
}
handleCustomerClick(customer) {
this.props.history.push(`/customers/${customer.id}`);
}
render() {
return(
<ul>
{ this.props.customers.map((c) =>
<li onClick={() => this.handleCustomerClick(c)} key={c.id}>
{c.name}
</li>
</ul>
)
}
}
//connect to redux to get customers
CustomersList = withRouter(CustomersList);
export default CustomersList;
The code is partial but illustrates perfectly the situation.
What happens is that the browser's address bar changes accordingly to history.push(..), but the view does not update, Customer component is not rendered and CustomersList is still there. Any ideas?
So I came to this question hoping for an answer but to no avail. I have used
const { history } = this.props;
history.push("/thePath")
In the same project and it worked as expected.
Upon further experimentation and some comparing and contrasting, I realized that this code will not run if it is called within the nested component. Therefore only the rendered page component can call this function for it to work properly.
Find Working Sandbox here
history: v4.7.2
react: v16.0.0
react-dom: v16.0.0
react-router-dom:
v4.2.2
It seems things have changed around a bit in the latest version of react router. You can now access history via the context. this.context.history.push('/path')
Also see the replies to the this github issue: https://github.com/ReactTraining/react-router/issues/4059
You can try to load the child component with history. to do so, pass 'history' through props. Something like that:
return (
<div>
<Login history={this.props.history} />
<br/>
<Register/>
</div>
)
For me (react-router v4, react v16) the problem was that I had the navigation component all right:
import { Link, withRouter } from 'react-router-dom'
class MainMenu extends Component {
render() {
return (
...
<NavLink to="/contact">Contact</NavLink>
...
);
}
}
export default withRouter(MainMenu);
Both using either
to="/contact"
or
OnClick={() => this.props.history.push('/contact')};
The behavior was still the same - the URL in browser changed but wrong components were rendered, the router was called with the same old URL.
The culprit was in the router definition. I had to move the MainMenu component as a child of the Router component!
// wrong placement of the component that calls the router
<MainMenu history={this.props.history} />
<Router>
<div>
// this is the right place for the component!
<MainMenu history={this.props.history} />
<Route path="/" exact component={MainPage} />
<Route path="/contact/" component={MainPage} />
</div>
</Router>
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.
import React, { Component } from 'react'
import { withRouter } from 'react-router';
// you can also import "withRouter" from 'react-router-dom';
class Example extends Component {
render() {
const { match, location, history } = this.props
return (
<div>
<div>You are now at {location.pathname}</div>
<button onClick={() => history.push('/')}>{'Home'}</button>
</div>
)
}
}
export default withRouter(Example)
Seems like an old question but still relevant.
I think it is a blocked update issue.
The main problem is the new URL (route) is supposed to be rendered by the same component(Costumers) as you are currently in (current URL).
So solution is rather simple, make the window url as a prop, so react has a chance to detect the prop change (therefore the url change), and act accordingly.
A nice usecase described in the official react blog called Recommendation: Fully uncontrolled component with a key.
So the solution is to change from
render() {
return(
<ul>
to
render() {
return(
<ul key={this.props.location.pathname}>
So whenever the location changed by react-router, the component got scrapped (by react) and a new one gets initiated with the right values (by react).
Oh, and pass the location as prop to the component(Costumers) where the redirect will happen if it is not passed already.
Hope it helps someone.
I had similar symptoms, but my problem was that I was nesting BrowserRouter
Do not nest BrowserRouter, because the history object will refer to the nearest BrowserRouter parent. So when you do a history.push(targeturl) and that targeturl it's not in that particular BrowserRouter it won't match any of it's route, so it will not load any sub-component.
Solution
Nest the Switch without wrapping it with a BrowserRouter
Example
Let's consider this App.js file
<BrowserRouter>
<Switch>
<Route exact path="/nestedrouter" component={NestedRouter} />
<Route exact path="/target" component={Target} />
</Switch>
</BrowserRouter>
Instead of doing this in the NestedRouter.js file
<BrowserRouter>
<Switch>
<Route exact path="/nestedrouter/" component={NestedRouter} />
<Route exact path="/nestedrouter/subroute" component={SubRoute} />
</Switch>
</BrowserRouter>
Simply remove the BrowserRouter from NestedRouter.js file
<Switch>
<Route exact path="/nestedrouter/" component={NestedRouter} />
<Route exact path="/nestedrouter/subroute" component={SubRoute} />
</Switch>
Let's consider this scenario. You have App.jsx as the root file for you ReactJS SPA. In it your render() looks similar to this:
<Switch>
<Route path="/comp" component={MyComponent} />
</Switch>
then, you should be able to use this.props.history inside MyComponent without a problem. Let's say you are rendering MySecondComponent inside MyComponent, in that case you need to call it in such manner:
<MySecondComponent {...props} />
which will pass the props from MyComponent down to MySecondComponent, thus making this.props.history available in MySecondComponent
You need to export the Customers Component not the CustomerList.
CustomersList = withRouter(Customers);
export default CustomersList;
I see that you are using a class component but in case you decide to switch to functional component or encountered the same issue with a functional component in your application, you can fix this issue by using the "useHistory" hook API by react-router-dom.
Example of usage:
import { useHistory } from "react-router-dom";
const Customers = ({customer}) => {
let history = useHistory();
const handleCustomerClick = (customer) => {
history.push(`/customers/${customer.id}`);
}
return (
//some JSX here
);
};
You may find the official documentation here: https://reactrouter.com/web/api/Hooks/usehistory
Beginner's mistake when working with routing is the importance of using withRouter directly with the component and not put any other high order component in between (or at least one that doest not know to push the props.history to its children:
Wrong: export default withRouter(withErrorHandler(Foo));
Correct: export default withErrorHandler(withRouter(Foo));
`const navigate=useNavigate();
navigate(/customers/${customer.id}); `
Don't use with Router.
handleSubmit(e){
e.preventDefault();
this.props.form.validateFieldsAndScroll((err,values)=>{
if(!err){
this.setState({
visible:false
});
this.props.form.resetFields();
console.log(values.username);
const path = '/list/';
this.props.history.push(path);
}
})
}
It works well.
You need to bind handleCustomerClick:
class Customers extends Component {
constructor() {
super();
this.handleCustomerClick = this.handleCustomerClick(this)
}
this.props.history.push(`/customers/${customer.id}`, null);

React routes not automatically updating when nested under 'smart' Redux container components

I'm trying to create an Electron app using React, React-router and Redux. What I'm finding is that my routing logic works absolutely fine when I'm nesting the switch/route logic under a purely presentational component (Page), but that I'm forced to refresh the page to see navigational changes if nested under a 'smart' container component.
Near the top of my React component hierarchy (right beneath HashRouter) I have a Page:
export default function Page (props) {
return (
<div className={`${styles.page}`}>
<SideBar/>
<DetailPane>{props.children}</DetailPane>
</div>
);
}
Here, DetailPane and SideBar are both container components wrapped around presentational components of the same name.
At startup (and during hot reloads), I create my React hierarchy using this function:
export default () => (
<Router>
<Page>
<Switch>
<Route exact path='/txDefinitions/:definitionName/:fieldName' component={FieldPage}/>
<Route exact path='/txDefinitions/:definitionName?' component={DefinitionPage}/>
<Route exact path='/rxDefinitions/:definitionName?' component={DefinitionPage}/>
<Route exact path='/'/>
<Route component={Route404}/>
</Switch>
</Page>
</Router>
This means that <Switch>...</Switch> gets nested underneath <DetailPane>.
If I try to navigate around my app (clicking links in the side bar), I won't actually see the detail pane render the new component until I force-reload the Electron app.
However, I find that routing works as expected if I omit DetailPane from Page:
export default function Page (props) {
return (
<div className={`${styles.page}`}>
<SideBar/>
{props.children}
</div>
);
}
Here is my React hierarchy without DetailPane (works fine):
Here is my React hierarchy with DetailPane (does not work right):
(Apologies for using images but I'm not sure if there's a way to copy from React devtools into clipboard - appears larger if opened in a new tab).
As I was writing this question, I realised this wouldn't be a huge issue for me because earlier refactoring had made the 'smart' version of DetailPane apparently obsolete. Using the purely presentational version of DetailPane
instead resolves this issue:
import * as React from 'react';
//import {DetailPane} from '../../containers'; // Smart/Redux
import {DetailPane} from '../../components'; // Dumb/presentational
import {SideBar} from '../../containers/';
const styles = require('./Page.scss');
export default function Page (props) {
return (
<div className={`${styles.page}`}>
<SideBar/>
<DetailPane>{props.children}</DetailPane>
</div>
);
}
However, I'm still curious why this doesn't work for the container component version. For reference, this is the container component version of DetailPane:
import {connect} from 'react-redux';
import {DetailPane} from '../../components';
// TODO: delete this container?
function mapStateToProps (state): {} {
return {};
}
function mapDispatchToProps (dispatch) {
// TODO.
return {};
}
export default connect(mapStateToProps, mapDispatchToProps)(DetailPane);
The connect HOC implements shouldComponentUpdate logic so if the props don't change, the component doesn't update.
To prevent this from occurring, and have the component always render, you can override the pure option in the connect call.
export default connect(mapStateToProps, mapDispatchToProps, undefined, { pure: false })(DetailPane);
See the react-redux API docs for more details.

ReactJS How to transfer data between pages?

I'm still new to React.
I was wondering, how do I transfer data from let's say "Main Page" to another page to display the results?
I think it has something to do with props? But I'm not entirely sure.
My MainPage has input/select tags that takes in name, value, selections from user and dates.
I want to be able to grab all those information and output it in another page called "DisplayResults" and maybe use the data for other things, maybe create a table with the information.
Thanks very much for your help!
This is my app.jsx
var React = require('react');
var ReactDOM = require('react-dom');
var {Route, Router, IndexRoute, hashHistory} = require('react-router');
var Main = require('Main');
var MainPage = require('MainPage');
var About = require('About');
// Load foundation
require('style!css!foundation-sites/dist/foundation.min.css')
$(document).foundation();
ReactDOM.render(
<Router history={hashHistory}>
<Route path="/" component={Main}>
<Route path="about" component={About}/>
<IndexRoute component={MainPage}/>
</Route>
</Router>,
document.getElementById('app')
);
There are a few different ways... Good option would be to use some state management layer like Redux or MobX
A simple one is to have your Main component save those data in its state and pass it to the other pages as props. Since you're using react-router you'll need to clone the this.props.children in your Main component to add the additional props like the data and the function that sets the data.
Your Main component would probably look something like
class Main extends Component {
constructor(props) {
this.state = {
data: 'some default data',
}
}
updateData(data) {
this.setState({ data });
}
render() {
return <div>
<Header />
{React.cloneElement(this.props.children, { data: this.state.data, setData: this.updateData })}
</div>
}
}
class MainPage extends Component {
render() {
return <div>
<input type="text" onChange={e => this.props.setData({ field: e.target.value })} />
<Link to="/DisplayResults">Go to Results</Link>
</div>
}
}
class DisplayResults extends Component {
render() {
return <div>
{this.props.data.field}
</div>
}
}
Technically React creates a single page application, therefore there is no other page to pass data to.
However, I believe you might be talking about passing data into components. I always structure my React applications into container and component folders. The container folder is where all the logic components are located, and the component folder is where all the UI components are located.
One of the things that makes React so intuitive is that you can easily pass data from parent components to children components via props. In other words, my logic components pass data to the UI components via props.
For example suppose I want my logic component called Dashboard to pass organization data to my UI component MyChildComponent, I would do something like this:
containers/DashBoard/index.js
export class DashBoard extends React.Component { // eslint-disable-line react/prefer-stateless-function
constructor(props) {
super(props);
this.state = {
organizations: null,
};
this.rowCallback = this.rowCallback.bind(this);
}
render() {
<MyChildComponent orgs={this.state.organizations} />
}
}
components/MyChildComponent/index.js
export class MyChildComponent extends React.Component {
constructor(props) {
super(props);
}
render(){
<div>
{this.props.orgs}
</div>
}
}
This is just one way to handle passing in data. You could also pass in values while routing between logic components, or use a flux library like redux to create state variables, etc..
Please note that my code excerpts make use of es6, and needs a babel
compiler. I also prefer using functions for UI components when
possible as I believe that is the React Way

Categories

Resources