create react app Jen

JenDiamond edited this page Jul 8, 2017 · 5 revisions

How to get "create-react-app" to work with your Rails API

git clone the repository

$ git clone git@github.com:fullstackreact/food-lookup-demo-rails

$ ls -1F

app/
bin/
client/
config/
config.ru
db/
flow-diagram.png
Gemfile
Gemfile.lock
lib/
log/
node_modules/
package-lock.json
Procfile
public/
Rakefile
README.md
test/
vendor/

This is where the server lives (aptly, server.js).

Inside of the db folder is a sqlite database containing the nutrition data. db/development.sqlite3

The package.json in this folder specifies the dependencies for the server. client/package.json

{
  "name": "lookup-client",
  "version": "0.1.0",
  "private": true,
  "proxy": "http://localhost:3001/",
  "devDependencies": {
    "enzyme": "2.4.1",
    "react-addons-test-utils": "15.4.0",
    "react-scripts": "0.8.5"
  },
  "dependencies": {
    "babel-plugin-transform-class-properties": "6.22.0",
    "react": "15.4.0",
    "react-dom": "15.4.0",
    "semantic-ui": "2.2.7"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}

I don't see server.js

Searching 1209 files for "server.js"
0 matches across 0 files

$ ls -1F client

package.json
public/
semantic/
semantic.json
src/

We have a Gemfile at the root of our app and a package.json in the client directory.
The client and the server specify their own dependencies independently.
For all intents and purposes, they are two completely separate apps that could exist in their own discrete git repos.

Let's install the dependencies for both:

Describe what is this doing

$ bundle && cd client && npm i && cd ..

+ npm WARN deprecated gulp-clean-css@2.4.0: breaking changes from clean-css 4.x. Please install gulp-clean-css 3.x
+ npm WARN deprecated minimatch@2.0.10: Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue
+ npm WARN deprecated minimatch@0.2.14: Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue
+ npm WARN deprecated graceful-fs@1.2.3: graceful-fs v3.0.0 and before will fail on node releases >= v7.0. Please update to graceful-fs@^4.0.0 as soon as possible. Use 'npm ls graceful-fs' to find it in the tree.
+ npm ERR! code E404
+ npm ERR! 404 Not Found: wrench@https://github.com/derekslife/wrench-js/tarball/156eaceed68ed31ffe2a3ecfbcb2be6ed1417fb2

+ npm ERR! A complete log of this run can be found in:
+ npm ERR!     /home/vagrant/.npm/_logs/2017-07-08T19_49_45_721Z-debug.log

Please install gulp-clean-css 3.x

gulp-clean-css
$ npm install gulp-clean-css --save-dev

$ npm update -g minimatch@3.0.2 $ npm update -g graceful-fs@^4.0.0

How do I fix these?


Create, migrate, and seed our API's database:

$ bundle exec rake db:create db:migrate db:seed

Boot from the top-level directory

$ rake start

Your browser should open localhost:3000 automagically.
Behold, the most advanced and nutritious interface V8 has ever rendered.

This is what returned when I ran rake start

$ rake start

20:26:16 web.1  | started with pid 8337
20:26:16 api.1  | started with pid 8338
20:26:18 web.1  | 
20:26:18 web.1  | > lookup-client@0.1.0 start /vagrant/food-lookup/client
20:26:18 web.1  | > react-scripts start
20:26:18 web.1  | 
20:26:18 web.1  | sh: 1: react-scripts: not found
20:26:18 web.1  | npm ERR! file sh
20:26:18 web.1  | npm ERR! code ELIFECYCLE
20:26:18 web.1  | npm ERR! errno ENOENT
20:26:18 web.1  | npm ERR! syscall spawn
20:26:18 web.1  | npm ERR! lookup-client@0.1.0 start: `react-scripts start`
20:26:18 web.1  | npm ERR! spawn ENOENT
20:26:18 web.1  | npm ERR! 
20:26:18 web.1  | npm ERR! Failed at the lookup-client@0.1.0 start script.
20:26:18 web.1  | npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
20:26:18 web.1  | npm WARN Local package.json exists, but node_modules missing, did you mean to install?
20:26:18 web.1  | 
20:26:18 web.1  | npm ERR! A complete log of this run can be found in:
20:26:18 web.1  | npm ERR!     /home/vagrant/.npm/_logs/2017-07-08T20_26_18_644Z-debug.log
20:26:19 web.1  | exited with code 1
20:26:19 system | sending SIGTERM to all processes
20:26:19 api.1  | terminated by SIGTERM

npm man pages

Wrench is a dependency that is messing up the semantic-ui

client/package.json
change "semantic-ui": "2.2.7" to "semantic-ui": "2.2.10"

Wrench Dependency from derekslife was removed in 2.2.8

See: 6fcbf43

Plus there's already a comment regarding this on that commit:  
6fcbf43#commitcomment-22596505

https://github.com/Semantic-Org/Semantic-UI/issues/5479
I urge you to use 2.2.10 instead of 2.2.7

AWESOME!!

I ran $ rake start and it ran!


Onward - Understand how this endpoint works

https://www.fullstackreact.com/articles/how-to-get-create-react-app-to-work-with-your-rails-api/#option-1-use-the-starting-point-branch

$ bundle exec rails s -p 3001 -b 33.33.33.33

http://33.33.33.33:3001/api/food?q=hash+browns

[{"description":"Fast foods, potatoes, hash browns, rnd pieces or patty","kcal":272.0,"fat_g":16.03,"carbohydrate_g":28.88,"protein_g":2.58},{"description":"Chick-fil-a, hash browns","kcal":301.0,"fat_g":17.36,"carbohydrate_g":30.51,"protein_g":3.0},{"description":"Denny's, hash browns","kcal":197.0,"fat_g":8.75,"carbohydrate_g":26.59,"protein_g":2.49},{"description":"Restaurant, family style, hash browns","kcal":197.0,"fat_g":8.75,"carbohydrate_g":26.59,"protein_g":2.49}]

build the front-end application

https://www.fullstackreact.com/articles/how-to-get-create-react-app-to-work-with-your-rails-api/#here-we-go

Ensure that you have create-react-app installed globally:

$ npm i -g create-react-app

And then at the top-level directory of the project we'll create our client app.
We want the React app to be in a folder called client,
so we'll just use that name in the create-react-app command:

$ create-react-app client

When I ran this I received this:
fsevents@1.1.2" The platform "linux" is incompatible with this module

I Googled it:
https://github.com/yarnpkg/yarn/issues/1285
Run:

$ yarn install --production

$ yarn install --production
yarn install v0.27.5
info No lockfile found.
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved lockfile.
Done in 1.04s.

This creates a new directory with the following file structure:

$ ls -1F client

node_modules/
package.json
public/
README.md
src/
yarn.lock

Taking a look at client/package.json, we note a single dev dependency and react and react-dom under dependencies

Mine looks like this (different from the tutorial)

client/package.json

{
  "name": "client",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^15.6.1",
    "react-dom": "^15.6.1",
    "react-scripts": "1.0.10"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}

What, exactly, is react-scripts?

react-scripts

react-scripts is an NPM package specifically for use with create-react-app.
It's the "black box" which contains the essentials:

  • Dependencies
    • Like Babel, ESLint, and Webpack.
  • Configuration
    • Config files for Webpack, Babel and ESLint, both for development and production.
  • Scripts
    • For instance, the command react-scripts start runs a script shipped with this package. It's responsible for ultimately booting the Webpack development server.

To see it in action, we can run npm start from inside of this folder:

$ cd client && npm start

Compiled successfully!

You can now view client in the browser.

  Local:            http://localhost:3000/
  On Your Network:  http://10.0.2.15:3000/

Note that the development build is not optimized.
To create a production build, use yarn build.

This will launch a Webpack dev server and should also open http://localhost:3000/ in your browser

We have our API server in the top-level directory and we were able to boot that. And we have our client app down here in client and we're able to boot a server for this.

But why does our React app need its own server? And how are we going to get these two servers working together?

Understanding this requires getting down to The Rub.

The Rub ™

Webpack is a JavaScript bundler. You might have many different JavaScript modules, like React components and Redux reducers, strewn across many different files. Webpack rolls these up into one gigantic "bundle." Think of the bundle as a combination of a vanilla index.html that includes a file bundle.js. This JavaScript file is one big, long file with every line of JavaScript that your app depends on, all shoved into one location. This single file will contain browser-ready JavaScript as Babel will have already worked its transpiling magic.

You can instruct Webpack that you would like it to produce this bundle.
create-react-app inserts a build command into package.json:

$ npm run build

This command kicks off Webpack and Webpack spits out a bundle.
You could then serve the index.html from that bundle wherever you'd like.

We could do this ( but we aren't going to )

We could use Webpack to generate this bundle.
And we could have our API server serve the static asset index.html.
We'd run the build command inside client/ And out would come the magic build folder.
We could then serve this folder with our API server.

It would work and we might even be happy about it.

However, there's a better approach

You can have Webpack boot a little Node server to serve the bundle. This means that when you make updates to your JavaScript/assets, you don't have to re-build and re-load — you just hit the server again to get the latest bundle. This workflow enables hot reloading, where your web app will reload itself when assets change, saving your modifier+R keys from a significant amount of wear.

In fact, if you run npm run build you'll find that this command is intended for production use. It does all kinds of optimization that can be time consuming — aggravating when you're quickly iterating in development. Booting a Webpack dev server is the way to go.

In production

we'll use npm run build to create our static bundle.
We can then throw that bundle anywhere (like S3), independent of the API server.
We'll explore this process in the next post.

So the user will direct their browser to localhost:3000, hitting the Webpack dev server. But then how will the React app communicate with our API server?

Our intuition would be to have a flow like this:

Flow diagram of an erroneous approach

In this flow, the user's browser makes a request to localhost:3000, loading the static assets from the Webpack dev server. The user's browser / React then makes requests as needed directly to the API server hosted on localhost:3001 with calls like this:

fetch('localhost:3001/api/foods?q=carrots', { // ... });

This would produce an issue, however. The React app (hosted at localhost:3000) would be attempting to load a resource from a different origin (localhost:3001). This would be performing Cross-Origin Resource Sharing. The browser prevents these types of requests from scripts for security reasons.

create-react-app provides a mechanism for working with an API server in development. We can have the Webpack development server proxy requests intended for our API server, like this:

Flow diagram of the approach with a proxy

In this flow, React makes an API request to localhost:3000, the Webpack development server. And then the development server simply proxies that request to the API server, negating any CORS issues.

So, the Rub: we need to (1) launch both the Webpack dev server and the API server in order to run the app locally. And then (2) we need to get the Webpack dev server to proxy requests intended for our API server.

For the first challenge, we could use two terminal windows: boot each server in its own window. But we could get a bit fancier. Foreman

Foreman is a utility for managing multiple processes. We'll see how it works by implementing it.

Insert foreman into your Gemfile:

gem 'foreman', '~> 0.82.0'

Install it with bundler:

$ bundle install

We then declare a Procfile, which specifies the commands Foreman should use to boot each of our desired processes. Let's create this file now:

$ touch Procfile

Open Procfile in your favorite editor. We'll declare two processes: one for web (our React app) and one for api (our Rails server):

web: cd client && npm start api: bundle exec rails s -p 3001

Save and close. We want Foreman to set process.env.PORT for our React app to 3000. We boot Foreman with this command:

$ foreman start -p 3000

The client app will boot — we can see it running in our browser at localhost:3000. And our API server is up and listening at localhost:3001. Hitting CTRL+C kills both processes together, humanely.

For our sanity, let's add a Rake task that executes this command for us. Create the file lib/tasks/start.rake:

task :start do exec 'foreman start -p 3000' end

We can now boot the app with:

$ rake start

With the foundations in place, let's wire the two up. We'll toss in the food lookup React components which will make requests against our API server. The app's React components

Let's steal a few files from the master branch of food-lookup-demo-rails.

If you're still inside that repository on the starting-point branch, you can run the following to grab these files from master:

git checkout master -- {client/src,client/semantic,client/semantic.json}

If instead you used rails new to start a new project from scratch, manually copy and paste those files from the repository.

We use Semantic UI for styling the app. It's included inside of src/index.js. index.css contains a few kludgey margins.

The meat is in Client.js and App.js.

For brevity, we won't walk through the React components. For the purposes of reading along, you just need to know that changing the value of the search bar ultimately calls search() on Client.

Client.js contains a Fetch call to our API endpoint:

function search(query) { return fetch(/api/food?q=${query}, { accept: 'application/json', }).then(checkStatus) .then(parseJSON); }

This is the one touch point between our React web app and the API server.

Notice how the URL does not include the base localhost:3001. That's because, as noted earlier, we want this request to be made to the Webpack development server. Thanks to the configuration established by create-react-app, the Webpack dev server will infer what traffic to proxy. It will proxy a request if the URL is not recognized or if the request is not loading static assets (like HTML/CSS/JS).

We just need to instruct Webpack to use the proxy.

If you're not coding along at home and want to take a peek at App.js, just check it out over on GitHub.

Setting up the proxy

To have the Webpack development server proxy all requests starting with /api to our API server, we just need to add the following line to client/package.json:

// Inside client/package.json "proxy": "http://localhost:3001/",

We're set. Testing it out

Our React app is ready and in place in client/. We have Foreman setup to boot both our Webpack dev server and our Rails API server together. And we've specified the route that Webpack should proxy API traffic to.

Let's boot both servers:

$ rake start

We're in business!

Final shot

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.