UI-Router for React - Hello Galaxy!

In the “Hello Solar System!” app, we created a list/detail interface. We showed a list of people and allowed the user to drill down to view a single person’s details. We implemented both the people and person states as siblings (peers to each other). When people was active, person could not be active, and vice versa.

In this section, we will create a parent-child state relationship by nesting the person state inside the people state. We will also nest their views, showing person details inside the people component.

Live demo

As usual, let’s first look at a live demo of the finished “Hello Galaxy” app.

Click the “People” tab, then click on a person.

When you switch from one state to the another, it is called a Transition. On the bottom of the live demo, the UI-Router Transition Visualizer shows each transition visually, and provides transition details when hovered and/or clicked.

Nesting States

UI-Router states form a tree, starting from a single root state. The root state is implicit and has no name. The top-level application states (such as about and people) are children of the implicit root state.

The “Hello Solar System” app had four top-level states, while the “Hello Galaxy” app has three top-level states and one nested state.

The person state is now a child of the people state.

The new person state definition:

const person = {
  name: 'people.person',
  url: '/:personId',
  component: Person,
  resolve: [{
    token: 'person',
    deps: ['$transition$', 'people'],
    resolveFn: (trans, people) =>
      people.find(person => person.id === trans.params().personId)
  }]
}

Changes to Person state definition We made some changes to the person state properties to make it a nested state… let’s go over each change.

State name

We changed the name: from person to people.person.

When naming a state, prepending another state’s name (and a dot) creates a parent/child relationship. In this case, the people.person state is a child of the people state.

Another way to create a parent/child relationship is with the parent: property of a state definition.

URL

We changed the url: from /people/{personId} to the url fragment /{personId}.

The child state’s url: property is a url fragment (a partial url). The full url for a child state is built by appending the child state’s url fragment to the parent state’s url.

The url for the parent state (people) is still /people. Appending /{personId} to /people results in /people/{personId} (which is the same as our previous person url).

The router will map a browser url of /people to the people state. It will map a browser url of /people/123 to the person state, with a peopleId parameter value of “123”.

Resolve

We changed the resolve: to use the data from the parent resolve, instead of fetching it from the server.

As before, when you click the “People” tab the router fetches the people resolve from the server API, then activates the people state and renders the view.

With our new nested state setup, the person resolve is a bit different. When we click a specific person, the router still invokes the person resolve before activating the person state. However, this resolve is a bit different. Instead of fetching the person from the server, the person resolve injects the parent state’s people resolve. Since the list of people is already loaded in the parent resolve, no additional fetching occurs.

A resolve function may inject the results of other resolves from ancestor states, or from other resolves on the same state.

Views

The view for person hasn’t changed. It is still a component named Person, which has a resolves attribute in its props. The resolve data named person is still passed via the component props.

However, the parent state’s People component has changed a bit.

render () {
  let {people} = this.props.resolves;
  
  let list = people.map(person => (
    <li key={person.id}>
      <UISref to=".person" params={{personId:person.id}}>
        <a>{person.name}</a>
      </UISref>
    </li>
  ));
  
  return (
    <div className={this.props.className}>
      <div className="flex-h">
        <div className="people">
          <h3>Some people:</h3>
          <ul>
            {list}
          </ul>
        </div>
        <UIView className="debug__UIView" />
      </div>
    </div>
  );
}

We’ve added some container divs for layout and embedded a nested <UIView> viewport. When a child state of people is activated, its view is put into the viewport. We also added some styling to create a side by side layout, so the nested viewport appears on the right.

Note the nested <UIView> when “People” is activated. That <UIView> is filled with the person view when the person state is active.

We also changed the UISref links in the people state which drill down to a specific person.

Previously, these links were <UISref to="person" params={{personId:person.id}}><a>{person.name}</a></UISref>. Since the person state is now named people.person, we changed the links to <UISref to="people.person" params={{personId:person.id}}><a>{person.name}</a></UISref>.

We could also have used relative addressing: <UISref to=".person" params={{personId:person.id}}><a>{person.name}</a></UISref>. Since the UISref was created in the people state’s view and it relatively targets .person, the final target state is people.person.

Updated: