React On Rails with Devise: Two Ways

Hi, I’m Kate! I’ve been working as a web development intern at Abletech for the last two months. Recently I’ve been investigating using React together with Ruby on Rails. My task was to discover common ‘gotchas’ and figure out what it would take to use React in our next project.

As a noob with a little experience with React, and even less with Rails, I ran into a bunch of errors and lost time trying to mush info from different tutorials together until I understood. I thought I’d write a few tutorials/explanations to help other baby devs become awesome!

This will be a two part tutorial series, exploring different ways to combine React with Rails.

We are going to be making an app with basic devise functionality. A user can sign in or sign up, change their password, logout or delete their account. It’s not the sexiest example, I know. But it will give you a foundation in both React and Rails, as you’ll be passing around state and props, rewiring some Devise controllers and making api calls within a React component.

You can look at the full repo here.

We’ll start with the most simple example: The react-rails gem. We’ll be rendering our components on the client side, without the use of a flux pattern to keep it easy. We’re going to use ES5 and Rails 5.0.2. (ES6 will be used in the second tutorial)

The react-rails gem is a great place to start, particularly if you are new to React. The setup is pretty minimal and will be familiar for rails enthusiasts. React components are added to your assets/javascripts folder, and the JSX (that weird mix of javascript and html in your components) is transformed using the ruby babel transpiler, so it ends up in the asset pipeline, just like if you were writing coffeescript for your Rails project. Basically it’s all the goodness of React without all the horror of webpack config.

This light-weight gem is a great way to experiment, but in a larger application it might end up holding you back. If you’re interested in going further with React, lookout for my next tutorial with the more powerful react_on_rails gem.

  1. Get the gems. We’re going to need Devise, and react-rails. Bundle install them.
  2. Generate a new controller called ‘Welcome’. This is going to be the root of our application. You can do this by running the command rails generate controller Welcome.This will create a corresponding Welcome folder inside the views. Inside this folder add a file index.html.erb.
  3. We need to make the index route our root by adding the line root: to => ‘welcome#index’ to the routes.rb file, inside the config folder. Double check that your welcomes_controller.rb file has an empty method called index.
  4. Create a Devise model for a User. I won’t linger on this one as Devise has it’s own getting started section on Github written by smarter people than me.

5. Migrate the database with: rails db:migrate

6. Install react with the command rails g react:install. This causes a bunch of great stuff to happen:

  • react and react_ujs are added to your application.js file
  • A components directory is created in app/assets/javascripts. This is where we write our React components. The file components.js is also added here. This requires our components tree.

5. Now we’ll write our first React component. Inside the components directory make a file called Welcome.jsx. Inside it write this:

var Welcome = React.createClass({
render: function () {
return <h1>Welcome!</h1>
}
})

6. Lastly, go to views/welcome/index and add:

<%= react_component(‘Welcome’) %>

Start your server and go to your localhost to check out your new component!

Cheeky Aside

If you want to pass values from your controllers into your React components you can do so with: <%= react_component(‘Welcome’, props: @object) %> the same way you pass values to your rails views.

So there you have it! The simplest React component ever. Don’t worry, now that we’re sure everything works we’re going to add some actual functionality so this app isn’t completely pathetic.

A blank page with ‘Welcome’ on it is pretty awesome, but it’s not exactly what we want. Let’s break the UI down into a story.

The user arrives on the login page and logs in. If they are a new user we want them to be able to change to the sign up page. Once a user is logged in they should see a form that allows them to edit their password. When logged in there should be buttons for signing out or deleting the account.

So that sounds like we have three possible pages or components inside of our main Welcome component: Login, Signup and Edit. We will also have two more components within the Edit component: Delete and Logout.

As we have multiple pages to our app we’re going to need to keep track of the page we’re on, and when something changes we’ll need to re-render the page with the new content. This is a great moment to learn about React and state.

What is State?

Literally: a javascript object with some component specific data in it. Conceptually: a description of your component at a point in time.

Basically a state is a javascript object with keys and properties. The state changes as the user moves through the app, in response to their actions. Changes to the state cause your React components and their children to re-render. If this makes you concerned about speed, read this explanation of React and the Virtual DOM.

Let’s go back to our Welcome component and make some changes.

var Welcome = React.createClass({ getInitialState: function() {
return {
page: ‘login’,
}
},
changePage: function(newPage) {
this.setState({
page: newPage
})
},
render: function() {
switch(this.state.page) {
case ‘login’:
return <Login changePage={this.changePage}/>
case ‘signup’:
return <Signup changePage={this.changePage}/>
case ‘edit’:
return <Edit changePage={this.changePage}/>
}
}
});

What’s going on here?

  • We are defining a function called getInitialState, and setting the page to ‘login’.
  • We also have a changePage function that takes newPage as a param and updates the state. Every time this.setState is called React re-renders the components and their children.
  • We have our render function, which now contains a switch statement. It checks the value of ‘page’ inside the state, and chooses the component to render based on that value. You can check this works by changing the value of page to ‘signup’ or ‘edit’. Make sure you have defined React components, and that they are returning something inside the render function, otherwise you’ll get mountains of errors.
  • Finally, we are passing the changePage function down to our child components, so they are able to update the page value in the application state. This function will be available to the children through this.props

Gotchas

Make sure that your react components have a capital letter as the class name. React uses capital letters to find components and lower case for html tags. If you’re missing a capital letter your components won’t render and there will be no error.

Want to add class based styling to your component? You need to add classes with the keyword className , as class is a reserved word in React.

Now write your Login, Signup and Edit components. See if you can include buttons that update the state using the change page function.

var Login = React.createClass({ render: function() {
return (
<div>
<h2>Login</h2>
<form>
<input id=’email’ placeholder=’email’/>
<input id=’password’ placeholder=’password’/>
<button>Submit</button>
</form>
<button onClick={()=>this.props.changePage(‘signup’)}>Sign Up!</button>
</div>
)
}
});

We pull the changePage function out of this.props and assign it to the onClick value of our button.We also have a form with a submit button that currently does nothing. We’ll get to that.

Now we have an app that can change pages based on the state. At this point it would be great to update our Devise controllers, so we can begin wiring up the backend with the front.

Basically all we need to do is change Devise so that it responds to json requests, and doesn’t attempt to redirect to a Devise view. There is a lot of information about how to do this, and in my opinion a lot of it is overly complicated. We’re going to keep it simple. Don’t change your application.rb, or start mucking around in the initialisers config like I did.

We are only going to update two of the controllers.

  1. Create a registrations_controller.rb and sessions_controller.rb. Copy and paste the the linked code. Or type it out if that’s your jam. Notice we are inheriting from the appropriate Devise controllers, rather than the ApplicationController.
  2. Update routes.rb. You need this line:
devise_for :users, controllers: { registrations: ‘registrations’, sessions: ‘sessions’ }

3. If it’s not already there add protect_from_forgery with: :null_session in your application controller. This will mean we need to send a CSRF token when accessing routes that need authentication which keeps the app secure.

If those routes are working the last thing we have to do is use ajax to tie our app together. Let’s look at an example for logging in.

var Login = React.createClass({ handleLogin(e) {
e.preventDefault()
var that = this
var userInfo = {
user: {
email: document.getElementById(‘email’).value,
password: document.getElementById(‘password’).value
}
}
$.ajax({
type: “POST”,
url: “http://localhost:3000/users/sign_in”,
dataType: 'json',
data: userInfo,
error: function (error) {
console.log(error)
},
success: function (res) {
that.props.changePage(‘edit’)
that.props.updateCurrentUser(res.email)
},
})
},
render: function() {
return (
<div>
<h2>Login</h2>
<form>
<input id=’email’ placeholder=’email’/>
<input id=’password’ placeholder=’password’/>
<button onClick={this.handleLogin}>Submit</button>
</form>
<button onClick={()=>this.props.changePage(‘signup’)}>Sign Up!</button>
</div>
)
}
});

Now when our submit button is clicked, it calls a function within the Login class called handleLogin.

We’ve also added another function to the props. ‘updateCurrentUser’ is defined inside the Welcome component. It updates a currentUser property in the state to an email. Then we pass the function into the child Login function.

case ‘login’:
return <Login
changePage={this.changePage}
updateCurrentUser={this.updateCurrentUser} />

When we hit the success callback in our ajax request our changePage and updateCurrentUser functions are called, the state is updated, and the components are re-rendered.

When you are writing your requests for your delete and edit account routes remember you’ll need your CSRF token. Something like this will do the trick:

$.ajaxSetup({
headers:
{ ‘X-CSRF-TOKEN’: $(‘meta[name=”csrf-token”]’).attr(‘content’) }
});

Gotchas

That e.preventDefault() line is pretty important. If you miss it your form will try to submit and the page will be refreshed, meaning your function won’t run and state won’t update. Basically nothing will happen and it will be horrifying.

This should be enough to get you started with React. Or if it’s not, here’s the repo. You know how to pass state and props around between child and parent components, and how to fragment the UI into bite sized pieces. You are probably starting to see how communication between large chains of components could get messy. You might be wondering what would happen if a component tried to talk to its sibling rather than a parent or child. Keep an eye out for my next tutorial, where i’ll use the react_on_rails gem with redux to find a (semi) elegant way to deal with those questions.

You probably noticed that my ajax error callbacks simply console.log errors, which isn’t particularly helpful. Use React to create a local state for the components, and update the UI with an error message if our ajax request fails. One trick I find useful for doing this is defining a class for styling with a turnery statement:

var errorClass = this.state.loginUnsuccessful ? ‘’ : ‘hidden'

If you enjoyed this blog post plz hit the heart button to help others find it. And if you’re really into it give me and my awesome workplace Abletech a follow!

Founder of Norquay Marketing. We help small businesses make money with Facebook advertising. I’m a proud New Zealander, obsessed with the internet and coffee