Resource IDs in routes

One common use case is to make the ID of a resource part of the URL. This makes it easy for your code to get the ID, then make an API call that fetches the relevant resource data. Let's implement a route that renders a user detail page. This will require a route that includes the user ID, which then needs to somehow be passed to the component so that it can fetch the user.

Let's start with the App component that declares the routes:

import React, { Fragment } from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';

import UsersContainer from './UsersContainer';
import UserContainer from './UserContainer';

export default () => (
<Router>
<Fragment>
<Route exact path="/" component={UsersContainer} />
<Route path="/users/:id" component={UserContainer} />
</Fragment>
</Router>
);

The : syntax marks the beginning of a URL variable. The id variable will be passed to the UserContainer component—here's how it's implemented:

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { fromJS } from 'immutable';

import User from './User';
import { fetchUser } from './api';

export default class UserContainer extends Component {
state = {
data: fromJS({
error: null,
first: null,
last: null,
age: null
})
};

// Getter for "Immutable.js" state data...
get data() {
return this.state.data;
}

// Setter for "Immutable.js" state data...
set data(data) {
this.setState({ data });
}

componentDidMount() {
// The dynamic URL segment we're interested in, "id",
// is stored in the "params" property.
const { match: { params: { id } } } = this.props;

// Fetches a user based on the "id". Note that it's
// converted to a number first.
fetchUser(Number(id)).then(
// If the user was successfully fetched, then
// merge the user properties into the state. Also,
// make sure that "error" is cleared.
user => {
this.data = this.data.merge(user, { error: null });
},

// If the user fetch failed, set the "error" state
// to the resolved error value. Also, make sure the
// other user properties are restored to their defaults
// since the component is now in an error state.
error => {
this.data = this.data.merge({
error,
first: null,
last: null,
age: null
});
}
);
}

render() {
return <User {...this.data.toJS()} />;
}
}

// Params should always be there...
UserContainer.propTypes = {
match: PropTypes.object.isRequired
};

The match.params property contains any dynamic parts of the URL. In this case, you're interested in the id parameter. Then, you pass the number version of this value to the fetchUser() API call. If the URL is missing the segment completely, then this code won't run at all; the router will revert back to the / route. However, there's no type-checking done at the route level, which means it's up to you to handle non-numbers being passed where numbers are expected, and so on.

In this example, the type cast operation will result in a 500 error if the user navigates to, for example, /users/one. You could write a function that type-checks parameters and, instead of failing with an exception, responds with a 404: Not found error. In any case, it's up to the application, not the react-router library, to provide a meaningful failure mode.

Now let's take a look at the API functions used in this example:

// Mock data...
const users = [
{ first: 'First 1', last: 'Last 1', age: 1 },
{ first: 'First 2', last: 'Last 2', age: 2 }
];

// Returns a promise that resolves the users array.
export function fetchUsers() {
return new Promise((resolve, reject) => {
resolve(users);
});
}

// Returns a promise that resolves to a
// user from the "users" array, using the
// given "id" index. If nothing is found,
// the promise is rejected.
export function fetchUser(id) {
const user = users[id];

if (user === undefined) {
return Promise.reject(`User ${id} not found`);
} else {
return Promise.resolve(user);
}
}

The fetchUsers() function is used by the UsersContainer component to populate the list of user links. The fetchUser() function will find and resolve a value from the users array of mock data or the promise is rejected. If rejected, the error-handling behavior of the UserContainer component is invoked. 

Here is the User component, responsible for rendering the user details:

import React from 'react';
import PropTypes from 'prop-types';
import { Map } from 'immutable';

// Renders "error" text, unless "error" is
// null - then nothing is rendered.
const Error = ({ error }) =>
Map([[null, null]]).get(
error,
<p>
<strong>{error}</strong>
</p>
);

// Renders "children" text, unless "children"
// is null - then nothing is rendered.
const Text = ({ children }) =>
Map([[null, null]]).get(children, <p>{children}</p>);

const User = ({ error, first, last, age }) => (
<section>
{/* If there's an API error, display it. */}
<Error error={error} />

{/* If there's a first, last, or age value,
display it. */}
<Text>{first}</Text>
<Text>{last}</Text>
<Text>{age}</Text>
</section>
);

// Every property is optional, since we might
// have have to render them.
User.propTypes = {
error: PropTypes.string,
first: PropTypes.string,
last: PropTypes.string,
age: PropTypes.number
};

export default User;

When you run this app and navigate to /, you should see a list of users that looks like this:

Clicking on the first link should take you to /users/0, which looks like this:

And if you navigate to a user that doesn't exist, /users/2, here's what you'll see:

The reason that you see this error message instead of a 500 error is because the API endpoint knows how to deal with missing resources:

if (user === undefined) {
reject(`User ${id} not found`);
}

This results in the UserContainer setting its error state:

fetchUser(Number(id)).then(
user => {
this.data = this.data.merge(user, { error: null });
},
error => {
this.data = this.data.merge({
error,
first: null,
last: null,
age: null
});
}
);

This then results in the User component rendering the error message:

const Error = ({ error }) =>
Map([[null, null]]).get(
error,
<p>
<strong>{error}</strong>
</p>
);

const User = ({ error, first, last, age }) => (
<section>
<Error error={error} />
...
</section>
);
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.104.248