Can the url params of the react-router <Route /> component be validated? - reactjs

I'm using react-router (2.4.1) and I have a wildcard (path="*") fallback route to display a "Not Found" component.
One of my routes are defined like:
<Route path="/campaigns/:category" component={CampaignsPage} />
Can I validate the category route param in any way to check if this category exist in my list of categories? and if not, I want the router to fall down to the wildcard in order to display the Not Found component

Maybe you could validate the received param inside the 'campaigns' component? Something along the lines of:
class CampaignsPage extends Component {
render() {
const validCampaignCats = [...]
const receivedCampaignCat = this.props.params.category
const renderChild = (actualCat, validCats) => {
if (!validCats.includes(actualCat)) {
return <YourErrorPage/>
}
return this.props.children
}
return (
<div>
<Header/>
{ renderChild(receivedCampaignCat, validCampaignCats) }
<Footer/>
</div>
)
}
}
Hope that helps!

Related

React router doesn't work correct with component

I'm trying to render container component if path matches.
class MyTopComponent extends Component {
componentDidUpdate() {
const params = getUrlParams(this.props.location);
if (params.id && params.id !== this.props.id) {
this.props.updateId(params.id);
}
}
render() {
const { a, b, c } = getUrlParams(this.props.location);
return this.props.id && this.props.isDataReady ? (
<div>
<Switch>
<Route path={MY_FIRST_PATH} component={MyContainer}/>
</Switch>
</div>
) : null;
}
}
The problem is that after going to path, then back, then to path again, page crashes.
It works fine when I use inline function in component:
<Route path={MY_FIRST_PATH} component={() => <MyContainer/>}/>
Is the second way good in performance?
If MY_FIRST_PATH is a constanct I assume it will resolve to a string i.e. 'MY_FIRST_PATH', but in your path prop you need to have path='/MY_FIRST_PATH'. Very likely you forgot your /

React-router and redux manual URL enter error

i have a problem of undefined props from redux store.
here is my routeHandler file
function organisationsFromStore(store) {
const { router, organisations } = store;
return {
organisations
}
}
function organisationFromStore(store) {
const { router, organisations } = store;
const { organisationId } = router.params;
return {
organisation: organisations.get(parseInt(organisationId))
}
}
export const organisationRouteHandler = connect(organisationFromStore)(Organisation);
export const accountsConfigurationRouteHandler = connect(organisationsFromStore)(AccountsConfiguration);
This is hooked to my getRoutes.jsx file which handles routes:
<Route path="accounts" component={accountsConfigurationRouteHandler}>
<Route path=":organisationId" component={organisationRouteHandler}></Route>
</Route>
In my Organisation.jsx(which gets organisations prop from it's parent AccountsConfiguration) component i have:
render() {
return (
<div style={{display: "inline"}}>
<div className={styles.desks}>
<span className={deskStyles.desksLabel}>Desks</span>
<ul className={deskStyles.desksList}>
{
this.props.organisation.get("desks").map(desk => {
return <li>{desk.get("name")}</li>;
})
}
</ul>
</div>
<div className={this.state.childrenStyle}>
{this.props.children}
</div>
</div>
);
When i thy to enter my URL manually e.g localhost:1234/accounts/4 (4 being the organisationId), i get the error saying this.props.organisations is not defined, which breaks the app. This is happening because first route's handler (organisationsFromStore) did not store the organisations and it didn't pass it as prop to AccountsConfiguration, which then didn't pass it to Organisations via this.props.children.
What is the best way to make the component wait for all the previous routes to get their props, and then render in this case Organisation component without error? Or is there a better way of doing this. Hope i was clear, thanks.
P.S I'm using old redux-router version before v2, and it must be that version at this time.
Ok, my mind wasn't working properly yesterday, i just inserted check if props are defined and rendered it conditionaly this.props.organisation ? .... : null

Passing data from child route to father route

I have a route structure like this:
<Route path="/master" component={MasterPageLayout}>
<IndexRoute path="/master/products" component={ProductsPage}/>
<Route path="/master/customer/:id" component={CustomerDetailsPage}/>
<Route path="/master/product/:id" component={ProductDetailsPage}/>
<Route path="/master/price-table" component={PriceTablePage} />
</Route>
<Route path="/poc" component={DistribuitorPageLayout}>
<IndexRoute path="/poc/inventory" component={InventoryPage}/>
</Route>
Inside the MasterPageLayout I have my header and my sidemenu (common to all the nested routes above him), the props.children is rendered inside those menus structures, but my header has a specific text for each route. How can I pass the text (and maybe some other data) from the child to the father?
Passing data back up the tree is usually handled with callbacks. As you only need to get the value once I'd recommend using one of the mounting lifecycle methods to call the callback.
As you've tagged react-redux, I'll give examples for both a React and Redux. I don't believe the basic react example is actually suitable for your situation, as you are rendering props.children which makes passing the callback down more difficult, but I'll leave it in the answer in case it's useful to someone else. The redux example should be applicable to your problem.
Basic React
You can pass a callback to the child that sets a value in the components state to use when rendering
class Child extends React.Component {
componentWillMount() {
this.props.setText("for example")
}
render() {
return (
<div>whatever</div>
)
}
}
class Parent extends React.Component {
render() {
return (
<div>
<Child setText={(text) => this.setState({text})} />
{this.state.text}
</div>
)
}
}
React/Redux
You could dispatch an action to set the text when the child is mounted that sets a value in the store to render in the parent, e.g.
class ChildView extends React.Component {
componentWillMount() {
this.props.setText("for example")
}
render() {
return (
<div>whatever</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
setText: (text) => dispatch(setParentText(text))
}
}
const Child = connect(null, mapDispatchToProps)(ChildView)
const ParentView = ({ text }) => {
return (
<div>
<Child />
{text}
</div>
)
}
const mapStateToProps = (state) => {
return {
text: state.parent.text
}
}
const Parent = connect(mapStateToProps)(ParentView)
I wont worry showing the action creator and the reducer/store setup. If you're using redux you should be able to figure that bit out.
This approach will also work if Parent doesn't directly render Child, whether it is through props.children or extra layers are introduced. In fact, Parent doesn't event need to be an ancestor of Child at all for this approach to work, as long as both are rendered on the same page.

react-router: Not found (404) for dynamic content?

How can react-router properly handle 404 pages for dynamic content in a Universal app?
Let's say I want to display a user page with a route like '/user/:userId'. I would have a config like this:
<Route path="/">
<Route path="user/:userId" component={UserPage} />
<Route path="*" component={NotFound} status={404} />
</Route>
If I request /user/valid-user-id, I get the user page.
If I request /foo, I get a proper 404.
But what if I request /user/invalid-user-id. When fetching the data for the user, I will realize that this user does not exist. So, the correct thing to do seams to be:
Display the 404 page
Return a 404 http code (for server side
rendering)
Keep the url as is (I don't want a redirect)
How do I do that?? It seams like a very standard behaviour. I'm surprised not to find any example...
Edit:
Seams like I'm not the only one to struggle with it. Something like this would help a lot: https://github.com/ReactTraining/react-router/pull/3098
As my app won't go live any time soon, I decided to wait to see what the next react-router version has to offer...
First of create a middleware function for the onEnter callback, so that this is workable for redux promises:
import { Router, Route, browserHistory, createRoutes } from "react-router";
function mixStoreToRoutes(routes) {
return routes && routes.map(route => ({
...route,
childRoutes: mixStoreToRoutes(route.childRoutes),
onEnter: route.onEnter && function (props, replaceState, cb) {
route.onEnter(store.dispatch, props, replaceState)
.then(() => {
cb(null)
})
.catch(cb)
}
}));
}
const rawRoutes = <Route path="/">
<Route path="user/:userId" component={UserPage} onEnter={userResolve.fetchUser} />
<Route path="*" component={NotFound} status={404} />
</Route>
Now in this onEnter function you can work directly with the redux store. So you could dispatch an action that either successes or fails. Example:
function fetch(options) {
return (dispatch) => {
return new Promise((resolve, reject) => {
axios.get('<backend-url>')
.then(res => {
resolve(dispatch({type: `CLIENT_GET_SUCCESS`, payload: res.data}))
})
.catch(error => {
reject(dispatch({type: `CLIENT_GET_FAILED`, payload: error}));
})
}
})
}
}
let userResolve = {
fetchUser: (dispatch, props, replace) => {
return new Promise((next, reject) => {
dispatch(fetch({
user: props.params.user
}))
.then((data) => {
next()
})
.catch((error) => {
next()
})
})
}
}
Whenever the resolve promise now fails, react-router will automatically look for the next component that it could render for this endpoint, which in this case is the 404 component.
So you then wouldn't have to use replaceWith and your URL keeps retained.
If you are not using server side rendering, returning 404 before the page gets rendered would not be possible. You will need to check for the existence of the user somewhere either way (on the server or via AJAX on the client). The first would not be possible without server side rendering.
One viable approach would be to show the 404 page on error of the Promise.
I tried my solution in a project that I am making which uses Server Side Rendering and react-router and it works there, So I'll tell you what I did.
Create a function in which you'll validate an ID. If the ID is valid, Then return the with User page with proper Component, If the ID is invalid then return the with 404 Page.
See the example:
// Routes.jsx
function ValidateID(ID) {
if(ID === GOOD_ID) {
return (
<Route path="/">
<Route path="user/:userId" component={UserPage} />
<Route path="*" component={NotFound} status={404} />
</Route>
);
} else {
return (
<Route path="/">
<Route path="user/:userId" status={404} component={Page404} />
<Route path="*" component={NotFound} status={404} />
</Route>
);
}
// Router.jsx
<Router route={ValidateID(ID)} history={browserHistory}></Router>
This should work with Server Side rendering as it did in my project. It does not uses Redux.
In case of dynamic paths, you can do it like this and you don't have to change the current path.
just import the error404 component and define a property(notfound) in the state to use for conditioning.
import React, { Component } from 'react';
import axios from 'axios';
import Error404 from './Error404';
export default class Details extends Component {
constructor(props) {
super(props)
this.state = {
project: {}, notfound: false
}
}
componentDidMount() {
this.fetchDetails()
}
fetchDetails = () => {
let component = this;
let apiurl = `/restapi/projects/${this.props.match.params.id}`;
axios.get(apiurl).then(function (response) {
component.setState({ project: response.data })
}).catch(function (error) {
component.setState({ notfound: true })
})
}
render() {
let project = this.state.project;
return (
this.state.notfound ? <Error404 /> : (
<div>
{project.title}
</div>
)
)
}
}
I encountered a similar problem while making a blog website. I've been searching for a solution for a while now. I was mapping (using map function) my blog component based on dynamic link.
The initial code snippet was as follows:
import Blog from '../../Components/Blog/Blog.component';
import './BlogPage.styles.scss';
const BlogPage = ({ BlogData, match }) => {
return (
<div className='blog-page'>
{
BlogData.map((item, idx)=>
item.link === match.params.postId?
<Blog
key={idx}
title={item.title}
date={item.date}
image={item.image}
content={item.content}
match={match}
/>
:''
)
}
</div>
)
};
export default BlogPage;
I used a hack where I would use filter function instead of map and store it and then check if it exists (in this case check if length greater than zero for result) and if it does the blog component is rendered with the props for the page else I render the Not Found component (My404Component).
The snippet as follows:
import Blog from '../../Components/Blog/Blog.component';
import My404Component from '../../Components/My404C0mponent/My404Component.component';
import './BlogPage.styles.scss';
const BlogPage = ({ BlogData, match }) => {
const result = BlogData.filter(item => item.link === match.params.postId);
console.log(result);
return (
<div className={result.length>0? 'blog-page': ''}>
{
result.length>0?
<Blog
title={result[0].title}
date={result[0].date}
image={result[0].image}
content={result[0].content}
match={match}
/>
:<My404Component />
}
</div>
)
};
export default BlogPage;
This way the Blog component is not rendered as long as the value of the entered link is not valid as result would be an empty array and it's length would be 0 and instead My404Component would be rendered.
The code is a little raw I havn't refactored it yet.
Hope this helps.

Get the current route name into the component

I am trying not to use : if(window.location) to get access to my current route.
I am working on a code where the router is already defined, and the component is already written. I am trying to access the router information from the component because I need to apply a classname depending on the route.
Here's how the router looks like:
export const createRoutes = (dispatch, getState) => (
<Route component={AppLayout} >
<Route path="/" component={Home} onEnter={(nextState, replaceState) => {
console.log('on Enter / = ', nextState);
nextState.test = 'aaaa'; //can't seem to pass states here....
}} />
then in my component:
render() {
//I want to access my super router or location . in fact
// I just want to know if currentURl === '/'
//and after reading the react-router doc 5 times - my head hurts
// but I still hope to get an answer here before putting a window.location
}
`
Depending on which version of React Router you're using you have access to different objects available on the context of a component.
MyComponent.contextTypes = {
location: React.PropTypes.object
}
which will enable you to do the following
render() {
if (this.context.location.pathname === '/') {
return <h1>Index</h1>;
} else {
return <h2>Not index</h2>;
}
}

Resources