/bluebird-9.0

Side Hustle of the TAs at App Academy 💸

Primary LanguageRuby

Bluebird & Thunk

Pre lecture

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.

1. Update webpack

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', '*']
  }
};

2. Update dependencies (package.json)

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;

3. Trim controllers and routes file

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'

4. Trim views and add static pages view

  • Keep layouts/*
  • add static_pages/root.html.erb in static_pages
  • trim layouts/application.html.erb to only have yield
  • delete users/*, chirps/* and sessions/* (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>

6. Frontend!

actions/chirpActions.js

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;

components/Root.jsx

import React from 'react';
import { Provider } from 'react-redux';

import App from './App';

const Root = ({ store }) => (
  <Provider store={store}>
    <App />
  </Provider>
);

export default Root;

components/App.jsx

import React from 'react';

import ChirpIndex from './chirps/ChirpIndex';
import ChirpForm from './chirps/ChirpForm';

const App = () => (
  <>
    <ChirpForm />
    <ChirpIndex />
  </>
);

export default App;

`components/chirps/*

You may want to refactor parts of these out into containers.

ChirpForm
// 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);
ChirpIndex
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);

reducers

rootReducer
import { combineReducers } from 'redux';

import chirpReducer from './chirpReducer';

const rootReducer = combineReducers({
  chirps: chirpReducer
});

export default rootReducer;
chirpReducer
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;
selectors

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;
};

store/store.js

import { createStore } from 'redux';

import rootReducer from '../reducers/rootReducer';

const configureStore = () => {
  return createStore(
    rootReducer
  );
};

utils/uniqueId.js

Note: Used before we integrate our FE with our BE.

export default function uniqueId() {
  return new Date().getTime();
}

export default configureStore;

entry

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);
});
  1. Make sure npm install, npm start and open index.html work.

  2. Additionally, make sure bundle install, rails db:setup and rails s works (you can see the react app at /).

  3. You will probs need to add gem jquery-rails to Gemfile, and then add //= require jquery to application.js (no need for jquery_ujs in Rails >= 5.1 if you have rails_ujs). https://github.com/rails/jquery-rails

  • Note: you need //= require jquery_ujs to prevent InvalidAuthToken errors.
  1. You may need to modify the create action in the chirps controller (I had to change user_id to author_id).