4 Commits

Author SHA1 Message Date
Mars Hall
b65b1b2a2a Switch to React functional component w/ hooks 2019-04-01 16:20:09 -07:00
Mars Hall
d0478f2723 Upgrade to React 16.8 2019-04-01 15:47:41 -07:00
Mars Hall
23cddeae9c Upgrade to React 16.8 2019-04-01 15:45:43 -07:00
Mars Hall
09e155d625 Switch to Heroku Node.js auto-build 2019-04-01 15:37:45 -07:00
6 changed files with 3526 additions and 2078 deletions

View File

@@ -16,9 +16,8 @@ A combo of two npm projects, the backend server and the frontend UI. So there ar
* [deployed automatically](https://devcenter.heroku.com/categories/deployment) via heroku/nodejs buildpack
2. [**React UI**](react-ui/): [`react-ui/package.json`](react-ui/package.json)
* generated by [create-react-app](https://github.com/facebookincubator/create-react-app)
* deployed via build hooks in the Node server's [`./package.json`](package.json)
* module cache configured by `cacheDirectories`
* bundling via `heroku-postbuild` hook
* deployed via `build` script in the Node server's [`./package.json`](package.json)
* module cache configured by `cacheDirectories`
Includes a minimal [Node Cluster](https://nodejs.org/docs/latest-v8.x/api/cluster.html) [implementation](server/index.js) to parallelize the single-threaded Node process across the available CPU cores.
@@ -41,7 +40,7 @@ This deployment will automatically:
* detect [Node buildpack](https://elements.heroku.com/buildpacks/heroku/heroku-buildpack-nodejs)
* build the app with
* `npm install` for the Node server
* `heroku-postbuild` for create-react-app
* `npm run build` for create-react-app
* launch the web process with `npm start`
* serves `../react-ui/build/` as static files
* customize by adding API, proxy, or route handlers/redirectors

6
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "heroku-cra-node",
"version": "1.0.0",
"version": "3.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -15,7 +15,7 @@
},
"array-flatten": {
"version": "1.1.1",
"resolved": "http://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
"body-parser": {
@@ -190,7 +190,7 @@
},
"media-typer": {
"version": "0.3.0",
"resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
"merge-descriptors": {

View File

@@ -1,13 +1,13 @@
{
"name": "heroku-cra-node",
"version": "1.0.0",
"version": "3.0.0",
"description": "How to use create-react-app with a custom Node API on Heroku",
"engines": {
"node": "10.x"
},
"scripts": {
"start": "node server",
"heroku-postbuild": "cd react-ui/ && npm install && npm install --only=dev --no-shrinkwrap && npm run build"
"build": "cd react-ui/ && npm install && npm install --only=dev --no-shrinkwrap && npm run build"
},
"cacheDirectories": [
"node_modules",

File diff suppressed because it is too large Load Diff

View File

@@ -3,9 +3,9 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^16.6.3",
"react-dom": "^16.7.0",
"react-scripts": "^2.1.2"
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-scripts": "^2.1.8"
},
"scripts": {
"start": "react-scripts start",

108
react-ui/src/App.js vendored
View File

@@ -1,18 +1,14 @@
import React, { Component } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
message: null,
fetching: true
};
}
function App() {
const [message, setMessage] = useState(null);
const [isFetching, setIsFetching] = useState(false);
const [url, setUrl] = useState('/api');
componentDidMount() {
fetch('/api')
const fetchData = useCallback(() => {
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(`status ${response.status}`);
@@ -20,54 +16,54 @@ class App extends Component {
return response.json();
})
.then(json => {
this.setState({
message: json.message,
fetching: false
});
setMessage(json.message);
setIsFetching(false);
}).catch(e => {
this.setState({
message: `API call failed: ${e}`,
fetching: false
});
setMessage(`API call failed: ${e}`);
setIsFetching(false);
})
}
}, [url]);
useEffect(() => {
setIsFetching(true);
fetchData();
}, [fetchData]);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
{ process.env.NODE_ENV === 'production' ?
<p>
This is a production build from create-react-app.
</p>
: <p>
Edit <code>src/App.js</code> and save to reload.
</p>
}
<p>{'« '}<strong>
{isFetching
? 'Fetching message from API'
: message}
</strong>{' »'}</p>
<p><a
className="App-link"
href="https://github.com/mars/heroku-cra-node"
>
React + Node deployment on Heroku
</a></p>
<p><a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a></p>
</header>
</div>
);
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
{ process.env.NODE_ENV === 'production' ?
<p>
This is a production build from create-react-app.
</p>
: <p>
Edit <code>src/App.js</code> and save to reload.
</p>
}
<p>{'« '}<strong>
{this.state.fetching
? 'Fetching message from API'
: this.state.message}
</strong>{' »'}</p>
<p><a
className="App-link"
href="https://github.com/mars/heroku-cra-node"
>
React + Node deployment on Heroku
</a></p>
<p><a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a></p>
</header>
</div>
);
}
}
export default App;