Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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...

like image 432
Tom Esterez Avatar asked Jan 20 '17 22:01

Tom Esterez


1 Answers

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.

like image 59
Kees van Lierop Avatar answered Nov 13 '22 15:11

Kees van Lierop