Porting React components from react_on_rails to React

Linked Projects:

Calreact
 
Lesson code: 

Backend Rails API - https://github.com/learnetto/calreact/tree/m9l1-rails-api

Frontend React app - https://github.com/learnetto/calreact-frontend/tree/m9l2-port-components

Now that we’ve set up our API and can talk to it from our new React app, we can port our React components from the Rails app into this new app.

First, copy over our component .jsx files from our Rails app to our React app.

I’ve created a components directory in the src directory of the React app, copied the component files in there and changed the file extensions form .jsx to .js. 

[screenshot: 0:23]

I’ve also copied the utils directory and files into src.

[screenshot: 0:30-31]

Now we need to install some of the extra libraries that we’re using.

First, let's add moment.js, using our console:

$ yarn add moment

And we also need immutability-helper. Then, we need react-router-dom, so add them in as well.

$ yarn add immutability-helper
$ yarn add react-router-dom

That way, we can use our components in the app. If I open up the index.html file, you will see that there’s a div with id root.

  <title>ReactApp</title>
    </head><body>
      <div id="root"></div>
   <!--
        This HTML file is a template.
        If you open it directly in the browser, you will see an empty page.
  
        You can add webfonts, meta tags, or analytics to this file.
        The build step will place the bundled scripts into the <body> tag.
  
        To begin the development, run `npm start`.
        To create a production bundle, use `npm run build`.
   -->
    </body>
 
This is where the React component App is being rendered.

Change the ReactApp title to CalReact. 

  <title>CalReact</title>
    </head><body>

If I open up the index.js file, you’ll see here we’re rendering the App component to its special div.

import React from 'react';
  import ReactDOM from 'react-dom';
  import App from './components/App';
  import './index.css';
  
  ReactDOM.render(
   <App />,
   document.getElementById('root')
  );

Change the name from App to AppRouter. Then we need to import it, using the following:

import React from 'react';
  import ReactDOM from 'react-dom';
  import AppRouter from './components/AppRouter';
  import './index.css';

Have a look at the browser to see whether it works. We get an error - $ is not defined in Appointments.js.

[screenshot: 1:29-30]

So let’s import that from jquery, using the command from App.js: 

import $ from 'jquery';

and placing it in Appointments.js: 

import React, { PropTypes } from 'react';
  import AppointmentForm from './AppointmentForm';
  import { AppointmentsList } from './AppointmentsList';
  import update from 'immutability-helper';
  import $ from 'jquery';

Still, we get the same error in Appointment.js. 

Let's fix it.

Then, in AppointmentForm.js, type in
 
import $ from jquery

Now we get a different error message: react-datetime not found.

[screenshot: 1:52-3]

It is because we haven’t installed that yet. Let’s do that and run add react-datetime to AppointmentForm.js in the console:

yarn add react-datetime

We could have copied the package.json file from our Rails app, but it has a lot of extra libraries which we either don’t need or which are already included by create react app.

Now, let’s get back to the app. See how it looks by refreshing the browser. The app is loading but the styles are still missing. We will get back to them.

We can also see that the data are not quite loading. We’re getting a 404 error. 

[screenshot 2:21]

That’s because we need to specify the full URL with the correct port in our AJAX calls in our Appointment.js file:

  componentDidMount () {
    if(this.props.match) {
      $.ajax({
        type: "GET",
        url: `http://localhost:3001/appointments/${this.props.match.params.id}`,
        dataType: "JSON"
      }).done((data) => {
        this.setState({appointment: data});
      });
    }
  }

As this is Appointment.js, this change will fix the show route. For the homepage, we need to add it in Appointments.js:

componentDidMount () {
    if(this.props.match) {
      $.ajax({
        type: "GET",
        url: `http://localhost:3001/appointments`,
        dataType: "JSON"
      }).done((data) => {
        this.setState({appointments: data});
      });
    }
  }

Now, let’s have a look at the browser. The web page loads.

Now, let’s add the CSS. Let’s go back to the Rails app and get to stylesheets by clicking app - assets - stylesheets (https://github.com/learnetto/calreact/tree/m9l1-rails-api/app/assets/stylesheets). Copy the stylesheet appointments.scss over to our components directory (https://github.com/learnetto/calreact-frontend/tree/m9l2-port-components/src/components).

And then we need to import the stylesheet into our app.

This is one benefit of using create react app. It comes with support for CSS modules so we can import CSS just like a JavaScript library.

In Appointments.js, add an extra line of code, linking to the stylesheet: 

import React, { PropTypes } from 'react';
import AppointmentForm from './AppointmentForm';
import { AppointmentsList } from './AppointmentsList';
import update from 'immutability-helper';
import $ from 'jquery';
import ‘./appointments.scss’

Refresh the page. It’s not working, probably because the stylesheet is a Sass file. 

[screenshot: 3:23]

We’re not using any Sass features anyway, so let’s just rename the file to appointments.css and change the import statement we already had:

import ‘./appointments.css’

Then the style loads.

But the calendar is still not getting styled. 

[screenshot: 3:40]

So let’s also copy react-datetime.css over to our components directory.

We use it in AppointmentForm.js, so let’s import it in there:

import React, { PropTypes } from 'react';
  import Datetime from 'react-datetime';
  import moment from'moment';
  import { validations } from '../utils/validations';
  import { FormErrors } from './FormErrors';
  import update from 'immutability-helper';
  import $ from 'jquery';
  import './react-datetime.css';

After that change, the calendar is formatted correctly. The center alignment is broken though because it's conflicting with index.css. 

To fix that, I’m just going to copy the CSS from appointments.css over to index.css:

body {
   width:500px;
   margin:auto;
   font-family: Arial, sans-serif;
   font-size: 14px;
   line-height: 1.5;
   padding-bottom: 200px;
  }
  
  a {
   color: darkred;
   text-decoration: none;
  }
  
  .submit-button {
   background: #CC0000;
   color: #FFFFFF;
   cursor: pointer;
   border: 0;
   box-shadow:none;
   border-radius: 0px;
   font-size: 20px;
   padding: 10px;
  }
  
  .submit-button:disabled {
   background: #BBBBBB;
   cursor: not-allowed;
  }
  
  .appointment {
   padding-top: 10px;
   border-bottom: solid 1px #ccc;
  }

Then we can delete appointments.css.

We can also remove the import statement:

import ‘./appointments.css’

Now the page loads with all the styles.

As the next step, test the form by putting some information into it, like the appointment title and date. 

The validation works.

And submitting the form is not yet going to work because we haven’t fixed the AJAX URLs in AppointmentForm.js.

[screenshot  4:41]

So to do that, let’s add localhost:3001 in all the AJAX calls, in componentDidMount, like in this example:

 componentDidMount () {
    if(this.props.match) {
      $.ajax({
        type: "GET",
        url: `http://localhost:3001/appointments/${this.props.match.params.id}`,
        dataType: "JSON"
      }).done((data) => {
        this.setState({appointment: data});
      });
    }
  }

Try to run the app again. And this time it works!

Now, click on an appointment link in the app. This isn’t quite working yet because we haven’t fixed the Show action in the controller.

[screenshot 5:16]

Let’s open appointments controller, then remove the HTML format and only render JSON:

class AppointmentsController < ApplicationController
   def index
   @appointments = Appointment.order('appt_time ASC')
   @appointment = Appointment.new
      render json: @appointments
   end
  
   def show
   @appointment = Appointment.find(params[:id])
      render json: @appointment
   end

Now if we reload the app, it will work. The rest of the actions are fine, they don't need any changes.

So now, let’s try editing an example appointment. Change the title and date and then submit the form by clicking on Update Appointment.

It didn’t work. Let’s check the Rails log. You get the RoutingError: No route matches OPTIONS.

[screenshot: 5:46]

This is happening because we haven’t listed patch as an action in our application configuration.

So let’s open up config/application.rb and then add :patch:

class Application < Rails::Application
    	config.api_only = true
      config.middleware.insert_before 0, Rack::Cors do
        allow do
          origins 'http://localhost:3000'
          resource '*', :headers => :any, :methods => [:get, :post, :patch, :delete, :options]
        end
   end

Then, we need to restart Rails and then we can try editing the appointment again. Change the title and date of the appointment and then submit.

Now it works.

Next, let’s try deleting an appointment.

It’ll fail because we haven’t added the delete action to our accepted list of methods. Let’s do that now.

First, restart Rails. Then, try testing the app again.

Click Delete appointment, then click OK.

And it works.

Now that everything works, we can remove the client directory from the Rails app.

We’ve got a front-end React app running on port 3000, talking to a Rails API app running on port 3001.

Liked this tutorial? Get more like this in your inbox