This lesson is for PRO members.

Unlock this lesson NOW!
Already subscribed? sign in

Building a React.js App: Transitions with React Router

5:30 React lesson by

In this video, we’ll walk through how to use React Router to transition from one Route to another while passing that new Route data via route parameters.

Get the Code Now
click to level up

egghead.io comment guidelines

Avatar
egghead.io

In this video, we’ll walk through how to use React Router to transition from one Route to another while passing that new Route data via route parameters.

Avatar
Ming

I found an issue with the state of the notelist component. When I transition the route to /profile/username from the github search bar, the first time the note list was updated from the firebase. But the second time when I type in the username in the search and the note list does not get updated. It remains the same for the last username. I need a way to refresh the note list component so that it reads the notes for the new username. Do you know how to do this? Thanks.

In reply to egghead.io
Avatar
Tyler

Ming, I'd check out the source code in the github for this specific video. I'm fairly confident I'm not having that issue in my code. Check specifically the componentWillReceiveProps life cycle method of this component. https://github.com/tylermcginnis/github-notetaker-egghead

In reply to Ming
Avatar
Ming

Thanks a lot, Tyler. That componentWillReceiveProps function was missing from my code and that did the trick! I watched this video a couple times and I didn't find that piece of code in there. Maybe you mention that in the following videos but I haven't gone through them. I checked out your code and found you use ES6 class syntax. Do you reckon I should use ES6 syntax or keep using the plain old functions and prototypes? ES6 class syntax does look more familiar to me from OO perspective. Thanks again for your super useful hint and the code.

In reply to Tyler
Avatar
Tyler

You probably checkout out the master branch, which is just the latest video in the series. If you keep watching you'll notice I refactor from ES5 to ES6 to show how it's done. I actually still use createClass in a lot of the projects I do. It just feels nicer and there are some things I don't have to worry about.

In reply to Ming
Avatar
Daniel

The tutorial does have an error, where Ming pointed it out. The branch on github does not include proper code to unbind the firebase DB and 'notes' state upon receiving props. code to add:

Add to Profile.js:
init: function() {
var childRef = this.ref.child(this.getParams().username);
this.bindAsArray(childRef, 'notes');
},

componentWillReceiveProps: function() {
    this.unbind('notes');
    this.init();
},
In reply to Tyler
Avatar
Philip

Hello. Nice tutorials, thanks Tyler. I believe getDOMNode() is deprecated and replaced with findDOMNode(). Just thought I'd let folks know :)

Avatar
Tyler

Thanks Philip! Dang breaking changes.

In reply to Philip
Avatar

Hi Tyler, thanks for the great lessons!

I'm having some issues with routing transitions (lesson no. 8). The js console output this error message when I focus on the search box in the top navigation bar (the SearchGithub component):

Warning: No route matches path "/profile/". Make sure you have <Route path="/profile/"> somewhere in your routes

I've tried to edit the routes.js files to get something like the following, but it doesn't solve the problem:

<Route name="/profile/" path="profile/:username" handler={Profile} />

If I manually change the URL to /profile/anyusername the Profile component is rendered properly, so I guess the problem is with the transition.

Do you have any idea?

In reply to Tyler
Avatar
dcarterjs

The componentWillReceiveProps is definitely not in the video nor in the example code up to this point.

In reply to Tyler
Avatar
Alex

Getting - this.transitionTo is not a function

Avatar
Chris Kihneman

I'm getting the same error.

In reply to Alex
Avatar
egghead.io

The lesson video has been updated!

Avatar
Zach

two questions:

First one is how do we get the enter event key to work automatically when we use the search bar in our main component to work right off the bat, but when we use the submit button on notes the enter button doesn't work, how would we implement this so when the user hits enter on his keyboard it triggers submit? and how come it just worked with our search submit ?

second questions is related to lesson 8 transition, once we hit submit and on the profile page, the search no longer works if we are on /profile/whateveruser, instead it adds another profile/whateveruser in addition to our current url, ex url /profile/whateveruser/profile/whateveruser, how do we update that so when we are on a certain profile we can still use the search box?

Avatar
Jeremy Zilar

Question about capitalization:
Why do you capitalize your filenames and folder names?

Avatar
Tyler

Just convention. I capitalize components and hence capitalize the file they go in.

In reply to Jeremy Zilar
Avatar
Tom Warhurst

I keep getting "Uncaught TypeError: Cannot read property 'value' of undefined", does anyone know what's wrong?

Avatar
Tyler

Have you checked your code against the solution branch? (https://github.com/tylermcginnis/github-notetaker-egghead/tree/08-transition) Also make sure all your versions match as well.

In reply to Tom Warhurst
Avatar
Jin

Router.History mixin has deprecated, wonder what;s the best way to do it now, push to browserhistory or conext router?

Avatar
Tyler

You can check out the changelog between React Router 1.0 and 2.0 here

In reply to Jin
Avatar
Sergei

For those that are doing this example on the latest version of react-router (2.0.x) which breaks this example, and apparently Mixins are officially deprecated (You get either error: [react-router] Router no longer defaults the history prop to hash history. Please use the hashHistory singleton instead. or [react-router] the History mixin is deprecated, please access context.router with your own contextTypes), I fixed this with in 2 ways:

Change app.js's render content to <Router history={hashHistory}>{routes}</Router>, and instead of using a mixing to navigate on SearchGithub.js , do:

var History = require('react-router').hashHistory;
{...}
History.pushState(...)

I really wish Tyler gave his opinion on this and the right way to do this on react-router 2.0.x. I think you can also do this with Contexts (but I haven't reached that point yet and this was the easiest solution at the time). Feel free to read more about this here: https://github.com/reactjs/react-router/blob/master/docs/guides/Histories.md#hashhistory

Avatar
Satyajeet

I tried the example. It says 'Cannot read property 'isRequired' of undefined'. Router.PropTypes doesn't have a 'router'.

In reply to Jason
Avatar
marlonjfrausto

I just ran into these issues and I wanted to thank you for hinting at a solution Sergei !
I ended up implementing a slightly different solution, hope this helps others save some time debugging.

-changes to your App.js file

import { hashHistory } from 'react-router'

ReactDOM.render(
<Router history={ hashHistory }>{routes}</Router>,
document.getElementById('app')
)

changes to your SearchGithub.js file (import statement goes along with your require statements):
...
import { hashHistory } from 'react-router';
...
hashHistory.pushState(null, "profile/" + username)
...

In reply to Sergei
Avatar
Jonny Adshead

Nice marlonjfrausto,
I found this really helpful for getting a few more bits of detail. https://github.com/reactjs/react-router-tutorial/tree/master/lessons/12-navigating

In reply to marlonjfrausto
Avatar
Mateusz Szymański

My handleSubmit function that works nice with React-router@2.8.1

e.preventDefault() will stop page from full reload that happened on chromium.

handleSubmit(e) {
    e.preventDefault();
    const username = this.usernameRef.value;
    this.usernameRef.value = '';
    Router.browserHistory.push(`/profile/${username}`);
  }
Avatar
Steven

Jason
Do you need to make any changes to App.js to get this to work?
I don't follow why this.context.router is defined in the component scope, can you please help me understand?
Also is it possible to use and if so what is best practice (and why?)?
Thanks!

In reply to Jason
Avatar
Alexius Hale-Dubuque

I'm seeing this error (Uncaught TypeError: Cannot read property 'pushState' of undefined) with the following line of code in SearchGithub.js:
"this.props.history.pushState(null, "profile/" + username).

Was there a solution given to this. My code is exactly like the solution code in github.

In reply to Tom Warhurst
Avatar
Alexius Hale-Dubuque

WOW, just like that I solved my issue. In SearchGithub.js:
Replace: "this.props.history.pushState(null, "profile/" + username)"
with this: "hashHistory.push(/profile/${username})"

In reply to Alexius Hale-Dubuque
Avatar
lnoogn

Thank you! This fixed my problem :)

In reply to Alexius Hale-Dubuque

As of right now, our menu we've been creating is just a bootstrap menu with a text menu. But what we actually want to have happen is we, as we've been doing, want to be able to enter a username, and when I click Search GitHub it goes and it transitions to this new route, passing along the username that we typed in. That's what we're going to do in this video.

Let's go ahead and go back to our code, and let's go ahead and make a new file inside of our components folder called SearchGitHub.js.

The very first thing, we're going to require('react'). Then what we're going to do is we're going to require the router, because we're going to rely on the router to actually do the transition for us between our routes, so require('react-router').

components/SearchGithub.js

var React = require('react');
var Router = require('react-router');

Then, as always, we make a component using React.createClass. Then we export this component. The mixins we're going to use for the transition is the history property on the router object. Then let's do a render function, which returns, it's going to return a form, so we're just going to have to bootstrap stuff here.

components/SearchGithub.js

var SearchGithub = React.createclass({
  mixins: [Router.History],
  render: function(){
    return()
  }
)};

module.exports = SearchGithub;

This form is, whenever it gets submitted, is going to run this.handleSubmit, which we'll create in a second. Then from here, we're going to have two <div>, one is going to be the input field, which is going to be of type="text" and a className="form-control". Then as we talked about in the last video, as a ref equal to this.getRef, which is going to call this function.

components/SearchGithub.js

return(
  <div className="col-sm-12">
    <form onSubmit={this.handleSubmit}>
      <div className="form-group col-sm-7">
        <input type="text" className="form-control" ref={this.getRef />}
      </div>
    </form>
  </div>
)

Then what we'll do is we'll have this input ref and let's go ahead and say this.usernameRef = ref.

components/SearchGithub.js

getRef: function(ref){
  this.usernameRef = ref;
}

The next container is going to be our button. Whoa. This button is going to be of type submit, have a class name of btn, btn-block, and we'll say btn-primary for the color and have a text of Search GitHub, and then we're going to close our button.

components/SearchGithub.js

<div className="form-group col-sm-5">
  <button type="submit" className=" btn btn-block btn-primary"> Search GitHub</button>
</div>

Now, what we need to do is we need to make a function called handleSubmit, that will get the value of our usernameRef and transition us to our profile route, passing along that username that we're getting from the ref.

components/SearchGithub.js

render: function() {
  return (
      <div>
        <form onSubmit={this.handleSubmit}>
          <div className="form-group col-sm-7">
            <input type="text" className="form-control" ref={this.getRef} />
          </div>
          <div className="form-group col-sm-5">
            <button type="submit" className="btn btn-block btn-primary"> Search GitHub </button>
          </div>
        </form>
      </div>
  )
}

Inside here, let's go ahead and create a handleSubmit method. As usual, let's go ahead and get the username, just as we did before. That's going to get us the username of this input field, or the value of the input field. Then let's go ahead and reset that to an empty string.

components/SearchGithub.js

handleSubmit: function(){
  var username = this.usernameRef.value;
  this.usernameRef.value = '';
  this.history.pushState(null, "profile/" + username);
}

Then, this is kind of where the magic happens.

Again, because we're using a mixin, what React is doing is it's taking any properties on the React.History modular object, that's adding it to our instance. One of those properties is called History, which has a property called pushState.

What we can do is pushState allows us to transition to a new route. We, the route we want to transition to is profile/ plus whatever this username is. Because if you'll remember a few videos ago in our routes.js config, we said, hey, whenever someone goes to profile/ whatever username, go ahead and render this profile component.

components/SearchGithub.js

handleSubmit: function(){
    var username = this.usernameRef.value;
    this.usernameRef.value = '';
    this.history.pushState(null, "profile/" + username);
  }

What we're doing here is we're saying, hey, whenever someone clicks on this handleSubmit button, go ahead and grab the username and then go ahead and take them to this profile/ whatever that username route is.

The last thing to do is, let's head over to our Main component. Instead of MENU here, we are going to render our <SearchGithub /> component, which we need to require.

components/Main.js

var React = require('react');
var SearchGithub = require('./SearchGithub')

var Main = React.createclass({
  render: function() {
    return (
      <div className="main-container">
        <nav className="navbar navbar-default" role="navigation">
          <div className="col-sm-7 col-sm-offset-2" style={{marginTop: 15}}>
            <SearchGithub />
          </div>
        </nav>
        <div className="container">
          {this.props.children}
        </div>
      </div>
    )
  }
});

Let's say SearchGithub equals, and we're in the same directory, so let's just get SearchGithub, and now let's check to see if this is working.

We head over here, we can search for a username and it should redirect us to /profile/ this username. Then our notes component renders, let's give this another test, and there we go.

Finished

HEY, QUICK QUESTION!
Joel's Head
Why are we asking?