Here's what was done for this cohort (Jan 2019) to prep Bluebird for this lecture. You can find the pre-lecture branch if you are interested, right before it's merged into master.
const path = require('path');
module.exports = {
entry: './frontend/entry.jsx',
output: {
path: path.resolve(__dirname, 'app', 'assets', 'javascripts'),
filename: './bundle.js'
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.jsx?$/, //a regular expression that tests what kind of files to run through this loader.
exclude: /(node_modules)/, //ignore these files
use: {
loader: 'babel-loader', //the name of the loader we are going to use (babel-loader).
query: {
presets: ['@babel/env', '@babel/react'] //
}
},
}
]
},
resolve: {
extensions: [".js", '.jsx', '*']
}
};
npm install react react-dom react-redux redux redux logger;
npm install -D webpack webpack-cli babel-loader @babel/core @babel/preset-env @babel/preset-react;
a. Remove all controllers (except application controller)
b. rails g controller api/chirps
c. rails g controller StaticPages
# controllers/api/chirps_controller.rb
class Api::ChirpsController < ApplicationController
def index
chirps = Chirp.all
render json: chirps
end
def create
chirp = Chirp.new(chirp_params)
chirp.user_id = User.all.sample.id
if chirp.save
render json: chirp
else
render json: chirp.errors.full_messages, status: 422
end
end
def chirp_params
params.require(:chirp).permit(:body)
end
end
# controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
def root
end
end
# In routes.rb
namespace :api, defaults: { format: :json } do
resources :chirps, only: [:index, :create]
end
root to: 'static_pages#root'
- Keep
layouts/*
- add
static_pages/root.html.erb
instatic_pages
- trim
layouts/application.html.erb
to only haveyield
- delete
users/*
,chirps/*
andsessions/*
(and anything else) - delete any views generated by controllers created in previous step
# static_pages/root.html.erb
<div id='root'><marquee>Where is React :(</marquee></div>
5. Add index.html
to top level directory. Note: this will be deleted in the demo, but you can start off the demo by opening this file in Chrome and showing how the components work. Then delete and rely on rails to serve this file (root.html.erb).
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>BlueBird</title>
<link rel="stylesheet" type="text/css" media="screen" href="./app/assets/stylesheets/application.css.scss" />
<script src="./app/assets/javascripts/bundle.js"></script>
</head>
<body>
<div id='root'></div>
</body>
</html>
export const RECEIVE_CHIRP = "RECEIVE_CHIRP";
export const RECEIVE_CHIRPS = "RECEIVE_CHIRPS";
export const receiveChirp = chirp => ({
type: RECEIVE_CHIRP,
chirp,
});
export const receiveChirps = chirps => ({
type: RECEIVE_CHIRPS,
chirps,
});
// TESTING
window.receiveChirp = receiveChirp;
window.receiveChirps = receiveChirps;
import React from 'react';
import { Provider } from 'react-redux';
import App from './App';
const Root = ({ store }) => (
<Provider store={store}>
<App />
</Provider>
);
export default Root;
import React from 'react';
import ChirpIndex from './chirps/ChirpIndex';
import ChirpForm from './chirps/ChirpForm';
const App = () => (
<>
<ChirpForm />
<ChirpIndex />
</>
);
export default App;
You may want to refactor parts of these out into containers.
// ChirpForm.jsx
import React from 'react';
import { connect } from 'react-redux';
import { receiveChirp } from '../../actions/chirpActions';
import uniqueId from '../../utils/uniqueId';
class ChirpForm extends React.Component {
constructor(props) {
super(props);
this.state = {
body: '',
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
const body = e.currentTarget.value;
this.setState({
body,
});
}
handleSubmit(e) {
e.preventDefault();
const chirp = Object.assign({}, this.state, { id: uniqueId() });
this.props.receiveChirp(chirp);
this.setState({ body: '' });
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
type='text'
onChange={this.handleChange}
value={this.state.body}
/>
<input
type='submit'
value='Share Chirp'
/>
</form>
);
}
}
const mapDispatchToProps = dispatch => {
return {
receiveChirp: chirp => dispatch(receiveChirp(chirp)),
};
};
export default connect(null, mapDispatchToProps)(ChirpForm);
import React from 'react';
import { connect } from 'react-redux';
import { selectAllChirps } from '../../reducers/selectors';
const ChirpIndex = ({ chirps }) => (
<ul className='chirp-index'>
{
chirps.map(chirp => (
<li key={chirp.id}>{chirp.body}</li>
))
}
</ul>
);
const mapStateToProps = state => {
return {
chirps: selectAllChirps(state),
};
};
export default connect(mapStateToProps)(ChirpIndex);
import { combineReducers } from 'redux';
import chirpReducer from './chirpReducer';
const rootReducer = combineReducers({
chirps: chirpReducer
});
export default rootReducer;
import { RECEIVE_CHIRP, RECEIVE_CHIRPS } from '../actions/chirpActions';
// ex: state
// todos: {
// 30: {
// id: 30,
// body: "This is a chirp"
// },
// 54: {
// id: 54,
// body: "Chirp chirp"
// }
// }
const chirpReducer = (state = {}, action) => {
Object.freeze(state);
let newState = Object.assign({}, state);
switch (action.type) {
case RECEIVE_CHIRP:
// structuring state to have outer keys (id) to access our chirps in O(1)
newState[action.chirp.id] = action.chirp;
return newState;
case RECEIVE_CHIRPS:
// also structuring state to have outer keys
for (let i = 0; i < action.chirps.length; i++) {
const chirp = action.chirps[i];
newState[chirp.id] = chirp;
}
return newState;
default:
return state;
}
};
export default chirpReducer;
Note: This prints the chirps in "reverse" order (most recent at top)
export const selectAllChirps = state => {
const chirpKeys = Object.keys(state.chirps);
const allChirps = [];
for (let i = chirpKeys.length - 1; i >= 0; i--) {
const chirpKey = chirpKeys[i];
const chirp = state.chirps[chirpKey];
allChirps.push(chirp);
}
return allChirps;
};
import { createStore } from 'redux';
import rootReducer from '../reducers/rootReducer';
const configureStore = () => {
return createStore(
rootReducer
);
};
Note: Used before we integrate our FE with our BE.
export default function uniqueId() {
return new Date().getTime();
}
export default configureStore;
import React from 'react';
import ReactDOM from 'react-dom';
import configureStore from './store/store';
import Root from './components/Root';
document.addEventListener('DOMContentLoaded', () => {
const root = document.getElementById('root');
const store = configureStore();
// TESTING
window.store = store;
ReactDOM.render(<Root store={store} />, root);
});
-
Make sure
npm install
,npm start
andopen index.html
work. -
Additionally, make sure
bundle install
,rails db:setup
andrails s
works (you can see the react app at/
). -
You will probs need to add
gem jquery-rails
to Gemfile, and then add//= require jquery
toapplication.js
(no need forjquery_ujs
in Rails >= 5.1 if you haverails_ujs
). https://github.com/rails/jquery-rails
- Note: you need
//= require jquery_ujs
to prevent InvalidAuthToken errors.
- You may need to modify the
create
action in the chirps controller (I had to changeuser_id
toauthor_id
).