I am trying to redirect to another path after receiving some information from the server, but It seems I can only use within , which I am not able to do.
the flow looks like this
1. client send socket message asking for a path
2. server look up a path, and send it back to client
3. client store the path into redux
4. redirect to the path
How can I achieve this?
Here's two ways you can redirect using react-router-dom, here's an example.
I suggest taking a look at the react-router docs here https://reacttraining.com/react-router/web/guides/philosophy
Method 1:
If you have a component that wraps your whole application use render and pass props for its arguments example.
https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/Route.md#render-func
//Wrapping component
import {BrowserRouter as Router, Route} from 'react-router-dom'
//Import your store ex:
{store} from '/path/to/store'
import Child from './components/child'
import Navbar from './components/navbar'
import Footer from './components/footer'
class App extends React.Component {
render() {
return(
<Provider store={store}>
<Router>
<div className="root">
<Navbar />
<Route exact path="/child" render={props => <Child {...props}/>}
//More routes.....
<Footer />
</div>
</Router>
</Provider>
)
}
}
//Child component
class SomeComponent extends React.Component {
constructor(props) {
super(props)
}
componentDidMount() {
socket.on('redirect', url => {
//dispatch your action to update store then redirect
this.props.history.push(url)
})
}
render() {
//some html
}
}
Method 2:
Store the redirect path in the store, and check if not null, you can use middleware, but for this example I'll use a simple if statement inside the component, the drawback using this method is you will need to dispatch an action after the redirect(you may run into some redirect issues if you don't) so it may be useful to wrap the redirect inside a method, and dispatch another action to set it back to null.
import {Redirect} from 'react-router-dom'
class SomeComponent extends React.Component {
render() {
if(this.props.redirectPath) return <Redirect to={this.props.redirectPath} />
else //Render your component
}
}
Related
I'm building a React app where I do NOT want the URL in the browser to be updated. I am NOT using 'react-router-dom' but only 'react-router' and MemoryRouter (https://reacttraining.com/react-router/web/api/MemoryRouter). The history.push() is available directly in the component statements but I wish to pass the history to children of children of these main components but the property is undefined.
Here is the Router section in main App.js (components Home and ScreeningTool can access this.props.history.push() as expected):
...
import {Route, MemoryRouter} from "react-router";
...
<MemoryRouter>
<Route exact path="/" component={Home} />
<Route path="/screening-tool" component={ScreeningTool} />
</MemoryRouter>
...
Both Home and ScreeningTool both use child component AppLink that generates a 'link' to navigate between Home and ScreeningTool like so (notice I'm passing 'history' as a prop):
Home.js:
...
<AppLink
url="/"
label="Screening Tool"
history={this.props.history}
/>
...
AppLink.js:
...
<div className="pseudo-link">
<span onClick={() => this.props.history.push(this.props.url)}>
{this.props.label}
</span>
</div>
...
The above code works. But there are children components in Home that will create their own AppLinks and also greatgrandchildren. I do NOT want to pass the history property as a component prop from Parent to Child to GrandChild components because this does not seem efficient. I have found the following stackoverflow questions but none of these options are working for me:
this.props.history.push works in some components and not others
react-router getting this.props.location in child components
I tried the newer 'userHistory' as described in the second URL above:
...
import { useHistory } from 'react-router';
...
render() {
let history = useHistory();
return (
<div className="pseudo-link">
<span onClick={() => history.push(this.props.url)}>
{this.props.label}
</span>
</div>
);
}
...
but I get Error: Invalid hook call. Hooks can only be called inside of the body of a function component..
I tried using withRouter as defined here https://reacttraining.com/react-router/web/api/withRouter but I get Error: Invariant failed: You should not use <withRouter(PseudoLink) /> outside a <Router>.
Finally, the accepted answer for this.props.history.push works in some components and not others ends with block of code export default withRouter(connect(mapStateToProps, matchDispatchToProps)(ChildView)); but does not explain where mapStateToProps or matchDispatchToProps comes from?
I'm thinking the issue is that I am using MemoryRouter and not normal/most common Router from 'reacto-router-dom'.
Can anyone help me out?
useHistory is a Hook so it should be used in a functional component, not inside a class based component.
Finally, the accepted answer for this.props.history.push works in some components and not others ends with block of code export default withRouter(connect(mapStateToProps, matchDispatchToProps)(ChildView)); but does not explain where mapStateToProps or matchDispatchToProps comes from?
-If you're not using redux then you can just use
export default withRouter(yourComponentName);
update
I've changed the AppLink component to this and it is working fine
import React from "react";
import "./AppLink.css";
import { useHistory } from 'react-router';
const AppLink = props => {
const history = useHistory();
return (
<div
className="app-link-button"
onClick={() => history.push(props.url)}
>
<span>{props.label}</span>
</div>
);
};
export default AppLink;
I want to create new Routes for my works web application, but the way that they are using the Routes is not the way I thought they would be used. Essentially I want to add new routes and be able to click on say like a button, and that will take me to the desired route that I want to go to.
The way it is set up in our application is that you actually have to manually go in the browser and type in the extended path to access that route, which doesn't seem like a good way of doing it.
Right now I have a route set up for our inventory system. You would access this route by typing in localhost::3000/inventory. There are buttons that come up on this main page which when clicked render that specific component. Instead of doing it that way, I would rather set up another route like /additem to the inventory path so that when I click on the Add Item button it will take me to the path localhost::3000/inventory/additem and render that component.
This is what our index.js file looks like for reference
import "babel-polyfill";
import 'core-js/es6/map';
import 'core-js/es6/set';
import 'core-js/fn/array/find';
import 'core-js/fn/array/includes';
import 'core-js/fn/number/is-nan';
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import ServiceReport from './imports/ServiceReportUI/ServiceReport'
import './StyleSheets/ServiceReport.css';
import InventorySystem from './imports/InventorySystem/InventorySystem.js';
import AddNewItemBtn from "./imports/InventorySystem/AddNewItemBtn";
import { BrowserRouter, Route, Link, Switch } from 'react-router-dom'
ReactDOM.render((
<BrowserRouter>
<Switch>
<Route exact path='/' component={App}/>
<Route path='/service' component={ServiceReport} />
<Route exact path='/inventory' component={InventorySystem} />
</Switch>
</BrowserRouter>), document.getElementById('appRoot'));
I'm thinking that adding another route like so will do the trick:
<Route exact path='/inventory/additem' component={AddItem} />
And I would access that route from within my InventorySystem.js file by doing something like this:
Class InventorySystem extends React.Component{
constructor(props){
super(props);
this.state = {}
}
goTo(e){
//go to Add Item path
}
render(){
return(
<button onClick={this.goTo.bind(this)}>Add Item</button>
)
}
}
I don't know much about React Router, and I'm also not sure if this is the right way of going about this, but any help or suggestions would be awesome!
The way to navigate within a React-Router setup is by using the Link component provided by the repo. Your first suggestion to create an additional Route for the AddItem component is correct. Simply import the Link component and define the expected path to go to.
import { Link } from "react-router-dom
class InventorySystem extends React.Component{
constructor(props){
super(props);
this.state = {}
}
render(){
return(
<Link to="/addItem">Add Item</Link>
)
}
}
You can style the Link to look like a button if needed as it does accept a className property.
You have 2 options, both included in example below
import { Link } from "react-router-dom";
class InventorySystem extends React.Component {
constructor(props) {
super(props);
this.state = {}
}
goTo(e) {
// option 1
this.props.history.push('/inventory/additem');
}
render() {
return (
<div>
<button onClick={this.goTo.bind(this)}>Add Item</button> // option 1
<Link to="/inventory/additem">Add Item</Link> // option 2
</div>
)
}
}
I'm quite new to reactjs and was just wondering if there is any easy way to display information from the same component to different routes. In the following code as an example I have just two functions that are just returning divs full of text, and calling them and rendering them right away (in the class or in the router) would just have them be on the same "page".
I've tried passing the ref by props but they always ended up undefined. I figured a state change would be awkward since there is no real "event". I'm using create-react-app, react-routerv4, and react-bootstrap.
In App.js
import React, { Component } from 'react';
import './App.css';
import NavBar from './Components/NavBar/NavBar.js';
import Band from './Components/Text/Band.js';
import { Router, BrowserRouter, Link, Route } from 'react-router-dom';
class App extends Component {
render() {
return(
<div>
<BrowserRouter>
<div className="RenderRouter">
<Route exact path='/' component={NavBar}/>
<Route exact path='/' component={ControlledCarousel}/>
<Route exact path='/' component={Home}/>
//<Route exact path='/Artists/ArtistX' component={Band}/>
<Route exact path='/Artists/Artist1' component={NavBar}/>
<Route exact path='/Artists/Artist1' render={props => <Band band1text = {this.props.band1text} />}/>
<Route exact path='/Artists/Artist2' component={NavBar}/>
<Route exact path='/Artists/Artist2' render={props => <Band band2text = {this.props.band2text} />}/>
</div>
</BrowserRouter>
</div>
);
}
}
export default App;
In Band.js
import React, { Component } from 'react';
import './Band.css';
class Band extends Component {
//Constructor for state change would go here
band1text(props) {
return(
<div id="band1Text" className="BandText">
<h1>"The best riffs!</h1>
</div>
);
};
band2text(props) {
return(
<div id="band2Text" className="BandText">
<p>More info coming soon! Check out the interview!</p>
</div>
);
};
//Create handlers to call functions, and pass reference?
render() {
return(
<div className="BandDescription">
//calling in DOM render object, can't pass props from here?
//{this.props.band1text()} = compiler error
{this.band1text()}
{this.band2text()}
</div>
);
}
}
export default Band;
It would probably be easier to just have separate components and classes for every piece of each route (i.e, BandX.js, CarouselX.js) but that could get verbose and one would have to import many files. I'm using react to build a music player component for the app as well, that's why I'm not just using standard JS.
Try writing something like this in your Band component render:
render() {
return(
<div className="BandDescription">
{this.props.band1text && this.band1text()}
{this.props.band2text && this.band2text()}
</div>
);
}
This way it checks for the prop before running whichever method. If both methods are passed, both functions will return. And you shouldn't need to pass props to those methods. Try writing them as arrow functions so they will be bound band1text = () => { ... }, you will still be able to access this.props.band1text from inside the method.
The props would be undefined because there is no props with bandText being passed down to App component. Routes are nested in App component and this.props.band1Text means you are expecting to read from props passed to App. Try passing band1Text and band2Text as props to App component.
Also to read a props that's not a function just use {this.props.band1Text} in the Band.js component
I am creating my first react app in electron (my first electron app too). I have two routes & need to navigate from one to another. For that I am using following code:
Root
ReactDOM.render(
<Router history={browserHistory}>
<App />
</Router>,
document.getElementById('root')
);
App
class App extends React.Component {
constructor() {
super();
}
render() {
return (
<div className="app-master">
<Switch>
<Route path='/city' component={CityList}/>
<Route path='/' component={SplashScreen}/>
</Switch>
</div>
)
}
}
Page
import { browserHistory } from 'react-router';
...
browserHistory.push('/city');
This line gives error,
TypeError: Cannot read property 'push' of undefined
I searched web for possible solution but can't find one! There are many similar questions on SO too, but none of it worked for me :(
You have to import it from the history module now which provides 3 different methods to create different histories.
createBrowserHistory is for use in modern web browsers that support the history API
createMemoryHistory is used as a reference implementation and may also be used in non-DOM environments, like React Native or tests
createHashHistory for legacy web browsers
You cannot use the browser history in an electron environment, use the hash or the memory one.
import { createHashHistory } from 'history'
const history = createHashHistory()
You can then use the history injected in the props
this.props.history.push('/')
Useful pointers above. The simplest solution I've found is to add:
import {createBrowserHistory} from 'history';
to your list of import statements, then add:
const browserHistory = createBrowserHistory();
Might not work perfectly, but for the basic stuff I'm working on seems to do the trick. Hope that helps.
Its is not working for your because in your component you are still using browserHistory which is not longer availabe from react-router package. You should change to using history from the history package
To simplify you can create a history.js file with the following contents
import { createBrowserHistory } from 'history'
export default createBrowserHistory();
Root
import history from '/path/to/history';
ReactDOM.render(
<Router history={history}>
<App />
</Router>,
document.getElementById('root')
);
Page
import history from 'path/to/history';
...
history.push('/city');
import { browserHistory } from 'react-router' does not work in React router 4. Link
Use the redirect component:
import { Redirect } from 'react-router';
<Redirect push to="/somewhere/else"/>
The render function should replace the entire content with Redirect component.
In react-router v4 initialize router as constant config and access the history through this.props in child components.
Import you dependecies
import { Route, Router } from "react-router";
import { createBrowserHistory } from "history";
Define your router config and add history as prop
const history = createBrowserHistory();
const routes = (
<Router history={history}>
<Route path='/city' component={CityList}/>
<Route path='/' component={SplashScreen}/>
</Router> )
class App extends Component {
render() {
return (
<div className = "app-master>
{routes}
</div>)
}
Defining route as a constant and out of render method this would initialize the route config only once.
Page Component
class Page extend Component {
render() {
this.props.history.push('/');
}
}
The history is now available as props in all the child components defined in routes config.
I need to pass props to component using router.
Here's my code:
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import AppBarTop from './appbar/AppBarTop';
import Login from '../pages/login/Login';
import {BrowserRouter as Router, Route} from 'react-router-dom';
class App extends Component {
render() {
const { isAuthenticated } = this.props;
return (
<Router>
<div>
<AppBarTop isAuthenticated={isAuthenticated} />
<div className="content">
<Route path="/login" isAuthenticated={isAuthenticated} component={Login} />
</div>
</div>
</Router>
);
}
}
As you can see, isAuthenticated the prop i want to pass to Login component.
class Login extends Component {
constructor(props) {
super(props);
console.log(props);
}
render() {
return (
<LoginForm />
);
}
}
export default connect(null) (Login);
When i log the props the isAuthenticated prop is not there. What i'm doing wrong? How can i pass the prop correctly?
I followed the docs and also other discussions. From my understanding it should work.
The version of react-router and react-router-dom is 4.0.0
Pass it like this:
<Route
path="/login"
render={(props) => <Login {...props} isAuthenticated={isAuthenticated}/>}
/>
It should be available by this.props.isAuthenticated in Login Component.
Reason of {...props}:
If we don't write this then only isAuthenticated will get passed to Login component, all other values that router passes to component, will not be available inside Login component. When we write {...props} then we are passing all the values with one extra value.
And instead of using component with router use render method.
As per DOC:
Component:
When you use component (instead of render or children, below) the
router uses React.createElement to create a new React element from the
given component. That means if you provide an inline function to the
component attribute, you would create a new component every render.
This results in the existing component unmounting and the new
component mounting instead of just updating the existing component.
When using an inline function for inline rendering, use the render.
Render:
This allows for convenient inline rendering and wrapping without the
undesired remounting.