Match the route with params by react-router v3 - javascript

Have a couple of routes with params (like. /user/:user, car/:car/:year)
I'm trying to avoid to manually parse location.pathname if it's possible to use react-router (v3) for it.
How can I find the route that match to the current url location.
Need something similar to:
if (router.match_to_route('/user/:user')) {
... do something
}
...
The method matchRoutes in https://github.com/ReactTraining/react-router/blob/v3/modules/matchRoutes.js might be the one that I need.
Thanks.
Updated:
It can be achieved by
import { match } from 'react-router';
const routes = router.routes;
match({ routes, location }, (error, redirect, renderProps) => {
const listOfMatchingRoutes = renderProps.routes;
if (listOfMatchingRoutes.some(route => route.path === '/user/:user')) {
...
}
}
https://github.com/ReactTraining/react-router/issues/3312#issuecomment-299450079

I have done this using react-router-dom. I simplified the code so that you can easily understand it. I just passed the param user with my dashboard route in main App component and access it using this.props.match.params.user in my child component named as Dashboard.
App.js file
class App extends React.Component {
constructor(props) {
super(props);
this.state = {open: false};
this.state = {message: "StackOverflow"};
}
render(){
return (
<Router>
<div>
<Route exact path="/dashboard/:user" render={props => <Dashboard {...props} />} />
<Route exact path="/information" render={props => <Information {...props} />} />
</div>
</Router>
);
}
}
Dashboard.js
import React from 'react';
class Dashboard extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div ><h1> Hello {this.props.match.params.user}</h1></div>
);
}
}
export default Dashboard;
I hope it will help you.

On RR4 you can use matchPath
const match = routes.find(route) => matchPath(props.location.pathname, {
path: route.path,
exact: true,
strict: false
})

Related

React JS - Redirect to login except home page

I'm learning to use React JS.
I have the following page.
Home
Login
Note
Create Note
My case is as follows.
Home can be accessed without logging in
Note and create notes cannot be accessed without logging in
How to make the case above work?
Here's the code snippet I made:
index.js
import App from "./App";
import * as serviceWorker from "./serviceWorker";
ReactDOM.render(
<BrowserRouter> // from "react-router-dom"
<App />
</BrowserRouter>,
document.getElementById("root")
);
serviceWorker.unregister();
App.js as entry home page
import React, { Component } from "react";
import AuthService from "./services/auth.service";
import Routes from "./config/routes";
// Lot of import bootstrap dan font-awesome and css
class App extends Component {
constructor(props) {
super(props);
this.logOut = this.logOut.bind(this);
this.state = {
currentUser: undefined,
backendSupportInformation: undefined,
};
}
componentDidMount() {
const user = AuthService.getCurrentUser();
if (user) {
this.setState({
currentUser: user,
backendSupportInformation: user.backend,
});
}
}
logOut() {
AuthService.logout();
}
render() {
const { currentUser, backendSupportInformation } = this.state;
return (
<div>
<header>
<nav className="navbar navbar-expand-sm navbar-dark bg-dark">
// some of link here
</nav>
</header>
<main role="main" className="container-fluid mt-3">
<Routes /> // use react-route-dom
</main>
</div>
);
}
}
export default App;
Routes.js
import React from "react";
import { Route, Switch } from "react-router-dom";
const Routes = () => {
return (
<Switch>
<Route exact path={["/", "/home"]} component={Home} />
<Route exact path="/login" component={Login} />
<Route exact path="/note" component={Note} />
<Route exact path="/note/create" component={NoteCreate} />
<Route exact path="/profile" component={Profile} />
</Switch>
);
};
export default Routes;
Now i am doing in NoteComponent like this.
NoteComponent
export default class Note extends Component {
state = {
redirect: null,
userReady: false,
};
componentDidMount() {
const currentUser = AuthService.getCurrentUser();
if (!currentUser) this.setState({ redirect: "/home" });
this.setState({ currentUser: currentUser, userReady: true });
this.retrieveAll();
}
render() {
if (this.state.redirect) {
// pass message that you need login first to access this note page
return <Redirect to={this.state.redirect} />;
}
}
I dont want to repeat my self into NoteCreate Component?
Any advice it so appreciated.
Just as a note to start, not sure which resources you're using to learn React, but as of now I would highly recommend you look into a modern course which teaches React with Hooks, aside from to get error boundaries (which with react-error-boundary) there is no reason to be writing class components.
Regarding the issue at hand, you didn't specifically mention any errors so this seems to be a question of "how should I go about this" as opposed to actually fixing something? Let me know if theres specific errors and I'll try to adjust my answer to help further.
I would recommend refactoring the logic you have in your Note component into a component of itself, so that you can wrap your routes with it. Store the information for whether they're authenticated into a context, and then wrap your routes with that context provider so you can consume that context in your child components, without duplicating that logic on each page.
You need to create a RouterWithAuth Component and use that instead of using Router directly, something like this:
export default class RouteWithAuth extends Component {
state = {
redirect: null,
userReady: false,
};
componentDidMount() {
const currentUser = AuthService.getCurrentUser();
if (!currentUser) this.setState({ redirect: "/home" });
this.setState({ currentUser: currentUser, userReady: true });
this.retrieveAll();
}
render() {
const { redirect, userReady } = this.state;
if (redirect) {
// pass message that you need login first to access this note page
return <Redirect to={this.state.redirect} />;
} else if (userReady) {
return (
<Route
exact={props.exact}
path={props.path}
component={props.component}
/>
);
} else {
return <div>Loading....</div>;
}
}
}
which a cleaner way of creating RouteWithAuth might be to use React Function Component like this:
export default function RouteWithAuth() {
const [redirect, setRedirect] = useState(null);
const [userReady, setUserReady] = useState(false);
useEffect(() => {
const currentUser = AuthService.getCurrentUser();
if (!currentUser) {
setRedirect("/home");
return;
}
//Do Something with the currentUser such as storing it in redux store or in context for later use cases
setUserReady(true);
}, []);
if (redirect) {
return <Redirect to={redirect} />;
} else if (userReady) {
return (
<Route
exact={props.exact}
path={props.path}
component={props.component}
/>
);
} else {
return <div>Loading....</div>;
}
}

React Authentication using HOC

The server sends a 401 response if the user is not authenticated and I was trying to check for authentication in the front end using a HOC as seen in Performing Authentication on Routes with react-router-v4.
However, I am getting an error saying TypeError: Cannot read property 'Component' of undefined in RequireAuth
RequireAuth.js
import {React} from 'react'
import {Redirect} from 'react-router-dom'
const RequireAuth = (Component) => {
return class Apps extends React.Component {
state = {
isAuthenticated: false,
isLoading: true
}
async componentDidMount() {
const url = '/getinfo'
const json = await fetch(url, {method: 'GET'})
if (json.status !== 401)
this.setState({isAuthenticated: true, isLoading: false})
else
console.log('not auth!')
}
render() {
const { isAuthenticated, isLoading } = this.state;
if(isLoading) {
return <div>Loading...</div>
}
if(!isAuthenticated) {
return <Redirect to="/" />
}
return <Component {...this.props} />
}
}
}
export { RequireAuth }
App.js
import React from 'react';
import { BrowserRouter as Router, Route, Switch, withRouter } from 'react-router-dom';
import SignIn from './SignIn'
import NavigationBar from './NavigationBar'
import LandingPage from './LandingPage'
import Profile from './Profile'
import Register from './Register'
import { RequireAuth } from './RequireAuth'
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Router>
<NavigationBar />
<Switch>
<Route exact path = '/'
component = {LandingPage}
/>
<Route exact path = '/register'
component = {Register}
/>
<Route exact path = '/profile'
component = {RequireAuth(Profile)}
/>
<Route path="*" component = {() => "404 NOT FOUND"}/>
</Switch>
</Router>
</div>
);
}
}
export default withRouter(App);
I can think of some possibilities:
------- Moved this to top which eventually fixed OP's issue -------
Try remove the curly braces at {React},
import React from 'react';
------- Moved this to top which eventually fixed OP's issue -------
In RequireAuth.js, Try
const RequireAuth = ({ Component }) => {} // changed from Component to { Component }
In App.js, use Component start with capital letter
<Route exact path = '/' Component = {LandingPage}/>
Also, in <Route path="*" component = {() => "404 NOT FOUND"}/>, looks like you're not passing in a React component because the function is not returning a JSX (I can't test now so I'm not very sure, though).
try this instead:
() => <div>404 NOT FOUND</div>
or if it doesn't work, define a functional component externally and pass into the Route:
const NotFoundComponent = () => <div>404 NOT FOUND</div>
<Route path="*" component = {NotFoundComponent}/>
try to do it like this:
const RequireAuth = ({ component: Component }) => {}

How to render state directly after setting it?

Introduction: I am very new to ReactJS(I just started a week ago), but I am not using that as an excuse, so please go hard on me, if there is something that I am not understanding.
Abstract: I am trying to implement Protected Routes.
Background: Upon the Protected Route component mounting, componentDidMount() invokes a function called isAuthenticated() that changes the state of a field called, isAuthenticated. That same field is what I am checking for to determine whether the user sees the Protected Component or is routed back to the Login page.
Issue: Everytime I visit the Protected Route aka CreatePost, my logs show that isAuthenticated() is invoked after componentDidMount() but I am unsure why my UI is not reflecting those new changes, to show the user the authenticated route.
Question: Can anyone please assist or recommend a better strategy that I have not considered? I really appreciate it.
Note: If I declare the this.state.isAuthenticated field in my constructor to be true, I will see my Protected Route, but this is not my intended goal. Hope this is helpful in diagnosing the issue.
App.js:
import React, {Component} from 'react'
import './App.css'
import Signup from './components/Signup'
import Login from './components/Login'
import Feed from './components/Feed'
import CreatePost from './components/Createpost'
import ProtectedRoute from './components/Protectedroute'
import {
BrowserRouter as Router,
Switch,
Link,
Route,
Redirect
} from 'react-router-dom'
class App extends Component {
render() {
return(
<Router>
<div>
<Link to ='/signup'>Signup</Link>
<br />
<Link to = '/login'>Login</Link>
<br />
<Link to ='/create-post'>Create Post</Link>
<br />
<Link to ='/feed'>Feed</Link>
</div>
<Switch>
<Route path = '/signup' component = {Signup} />
<Route path = '/login' component = {Login} />
<Route path = '/feed' component = {Feed} />
<ProtectedRoute path = '/create-post' component = {CreatePost} />
</Switch>
</Router>
)
}
}
export default App
ProtectedRoute.js
import React, {Component} from 'react'
import { Redirect } from 'react-router-dom'
class ProtectedRoute extends Component {
constructor(props) {
super(props)
this.state = {
isAuthenticated: false
}
this.isAuthenticated = this.isAuthenticated.bind(this)
}
componentDidMount() {
console.log('--componentDidMount--')
this.isAuthenticated()
}
isAuthenticated() {
console.log('--isAuthenticated--')
this.setState({isAuthenticated: true})
}
render() {
const Component = this.props.component
return this.state.isAuthenticated ? (<Component />) : (<Redirect to = '/login' />)
}
}
export default ProtectedRoute
Instead of setting state inside the component for that api call try creating a function and then returning the api response as true or success
import React, { Component } from "react";
import { Redirect, Route } from "react-router-dom";
class ProtectedRoute extends Component {
constructor(props) {
super(props);
this.state = {
isAuthenticated: false
};
this.isAuthenticated = this.isAuthenticated.bind(this);
}
componentDidMount() {
console.log("--componentDidMount--");
this.isAuthenticated();
}
isAuthenticated() {
console.log("--isAuthenticated--");
// Authentiction logic
return true;
}
render() {
const Component = this.props.component;
return (
<Route
render={props =>
this.isAuthenticated() ? (
<Component {...props} />
) : (
<Redirect to="/login" />
)
}
/>
);
}
}
export default ProtectedRoute;
Seems like the problem here that setState is async. What happens is that in the first render this.state.isAuthenticated is false and therefore immediately redirecting. You can introduce a loading state which will help you defer the rendering of the component \ redirecting to only after the state of both loading and isAuthenticated are settled.
In general never expect setState to change the state immediately in a synchronous way.

Pass props or data beteween components React

Good afternoon friends,
My pages and components are arranged in the main class of my application, can I pass some results from any component or page to the main class and get this property from main class to any other component.
To describe question well I will show an example:
This is my main class App.js
import React, {Component} from 'react';
import { BrowserRouter as Router, Route} from "react-router-dom";
import HomePage from "./Pages/HomePage";
import NavBar from "./Components/NavBar";
import PaymentStatus from "./Pages/PaymentStatus";
class App extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
};
render() {
return (
<Router>
<NavBar/>
<Route name={'Home'} exact path={'/'} component={HomePage}/>
<Route name={'PaymentStatus'} exact path={'/payment-status/:tId'} component={PaymentStatus}/>
</Router>
);
}
}
export default App;
Now my navigation bar component: NavBar.js
import React, {Component} from 'react';
class NavBar extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
};
_makeSomething =async() => {
// Somw function that returns something
}
render() {
return (
<div>
<div id={"myNavbar"}>
<div>
<a onClick={()=>{this._makeSomething()}} href={'/'}/> Home</a>
<a onClick={()=>{this._makeSomething()}} href={"/payment-status"} />Payment Status</a>
</div>
</div>
);
}
}
export default NavBar;
HomePage.js
import React, {Component} from 'react';
class HomePage extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
};
async componentDidMount() {
console.log(this.props.match.params.tId)
};
render() {
return (
<div>
<div id={"main"}>
<div>
<p>This is home page</p>
</div>
</div>
);
}
}
export default HomePage;
PaymentStatusPage.js
import React, {Component} from 'react';
class PaymentStatusPage extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
};
async componentDidMount() {
console.log(this.props.match.params.tId)
};
render() {
return (
<div>
<div id={"status"}>
<div>
<p>This is payment Status Page</p>
</div>
</div>
);
}
}
export default PaymentStatusPage;
Now here is the question:
Can I pass to App.js events (or props) when HomePage.js or PaymentStatusPage.js or when something was changed in NavBar.js
Also, want pass received peprops to any component.
Thank you.
You can decalare method in class App and then pass it to another component via props.
For example
Then you can call this method in MyComponent and pass some value to it. This is the way you pass value from subcomponent to parent component. In method in App you can simply use setState.
What's left to do is to pass this new state attribute to another component via props.
To pass value to component, while using you have to change
<Route component={SomeComponent}
To
<Route render={() => <SomeComponent somethingChanged={this.somethingChangedMethodInAppClass}}/>
Hope it helps!
EDIT: You can also use Redux to externalize state and reuse it in child components
You have two options here:
Keep all of your state in your parent component, App, and pass any props down to your children component, even actions that could update the parent state. If another children uses that state, then that child will be rerendered too.
Manage your state with Redux and make it available for all your components.
I created a small example out of your scenario.
In this example, the App component has a state with a property called title and a function that is passed down via props to the Navbar.
class App extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {
title: "Home Page"
};
}
_makeSomething = title => {
this.setState({ title: title });
};
render() {
return (
<Router>
<NavBar clicked={this._makeSomething} />
<Route
name={"Home"}
exact
path={"/"}
component={() => <HomePage title={this.state.title} />}
/>
<Route
name={"PaymentStatus"}
exact
path={"/payment-status/:tId"}
component={() => <PaymentStatus title={this.state.title} />}
/>
</Router>
);
}
}
The components HomePage and PaymentStatus will get that title as props from the App's state and NavBar will get the _makeSomething function as props. So far, all that function does is update the state's title.
class NavBar extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
}
render() {
return (
<div>
<div id={"myNavbar"}>
<NavLink
onClick={() => {
this.props.clicked("Home Page");
}}
to={"/"}
>
{" "}
Home
</NavLink>
<NavLink
onClick={() => {
this.props.clicked("Payment Page");
}}
to={"/payment-status/1"}
>
Payment Status
</NavLink>
</div>
</div>
);
}
}
In the Navbar, when the function I passed down from App as props is clicked, it will go all the way back to the App component again and run _makeSomething, which will change the App's title.
In the mantime, the components HomePage and PaymentStatus received title as props, so when the state's title is changed, these two children component will change too, since their render function relies on this.props.title.
For example, HomePage:
class HomePage extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
}
async componentDidMount() {
console.log(this.props.match.params.tId);
}
render() {
return (
<div>
<div id={"main"}>
<p>This is {this.props.title}</p>
</div>
</div>
);
}
}
Like I said before, by keeping your state in the parent component and sending down to the children component just what they need, you should be able to accomplish what you need.
A note: I did change the anchor tag from <a> to NavLink which is what you're supposed to use with react-router-dom if you don't want a complete refresh of the page.
The full code can be found here:
Have a look at Context. With this you can pass an object from a Provider to a Consumerand even override properties with nested providers: https://reactjs.org/docs/context.html
AppContext.js
export const AppContext = React.createContext({})
App.js
someFunction = ()=>{
//implement it
}
render() {
const appContext = {
someFunction: this.someFunction
}
return (
<AppContext.Provider value={appContext}>
<Router>
<NavBar/>
<Route name={'Home'} exact path={'/'} component={HomePage}/>
<Route name={'PaymentStatus'} exact path={'/payment-status/:tId'} component={PaymentStatus}/>
</Router>
</AppContext>
);
}
Homepage.js
class HomePage extends Component {
constructor(props) {
super(props);
this._isMounted = true;
this.state = {};
};
async componentDidMount() {
console.log(this.props.match.params.tId)
this.props.appContext.someFunction(); //calls the function of the App-component
};
render() {
return (
<div>
<div id={"main"}>
<div>
<p>This is home page</p>
</div>
</div>
);
}
}
export default (props) => (
<AppContext.Consumer>
{(appContext)=>(
<HomePage {...props} appContext={appContext}/>
)}
</AppContext.Consumer>
)
You can also use this mechanic with function components. I'm normally encapsulating the Consumer to an extra component. So all values available for the component as normal property and not just inside the rendered components.

How to use url params in separate component

I've been learning React over the last 2 days, and I'm having trouble with understanding URL parameters.
Let's say I want a route mysite.com/details/1023. The route is defined like:
<Route path="/details/:id" render={() => <DishDetails />}/>
Now I want the DishDetails object, which is defined in another file, to be able to use the id value 1023. How can I do this? I've found tutorials on route url params but none that explains how to achieve this.
Here's what my DishDetails view looks like right now:
import React, {Component} from "react";
import "./DishDetails.css";
import {Link} from "react-router-dom";
class DishDetails extends Component {
constructor(props) {
super(props);
this.state = {
id: /*url param*/,
};
}
render() {
return this.state.id;
}
}
export default DishDetails;
Where can I get the id in DishDetails? I've tried:
import React, {Component} from "react";
import "./DishDetails.css";
import {Link} from "react-router-dom";
class DishDetails extends Component {
constructor(props) {
super(props);
this.state = {
id: match.id,
};
}
render() {
return this.state.id;
}
}
But match is undefined.
Pass your component to Route via the component props:
<Route path="/details/:id" component={DishDetails} />
If you did this match is available in the props.
If you have to keep the way how you render your routes you can pass the render props manually to your component:
<Route path="/details/:id" render={(props) => <DishDetails {...props} />}/>
You can find the whole documentation for react-router here.
The <Route render> prop receives the router props:
match
location
history
You need to provide that props to the <DishDetails> component and use the match.params.id to retrieve the id from your path="/details/:id"
const DishDetails = props => <div>Details Id: {props.match.params.id}</div>;
<Route path="/details/:id" render={props => <DishDetails {...props} />} />
This is the Route props in your example:
{
match: { path: '/details/:id', url: '/details/1', isExact: true, params: { id: '1' } },
location: { pathname: '/details/1', search: '', hash: '' },
history: { length: 3, action: 'POP', location: { pathname: '/details/1', search: '', hash: '' } }
}
There are 3 ways to render something with a <Route>:
<Route component>
<Route render>
<Route children>
Read more here
Have you tried the withRouter function that comes with react-router?
import { withRouter } from 'react-router'
class App extends Component {
.... your stuff
}
export default withRouter(App);
That should give you access to the "match" prop

Categories