Skip to content

Commit

Permalink
WIP for moving header to React with shared store
Browse files Browse the repository at this point in the history
Bugs and TODOs:
1. Turbolinks is preventing the proper header link changes. If you open
links in different tabs, you get mostly correct behavior.
2. When you open the /simple in a different tab, the header is not
highlighted.
  • Loading branch information
justin808 committed Apr 14, 2016
1 parent 97544c8 commit 163f1ad
Show file tree
Hide file tree
Showing 29 changed files with 605 additions and 433 deletions.
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
5.0.0
5.10.0
2 changes: 0 additions & 2 deletions app/assets/javascripts/application_non_webpack.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// All webpack assets in development will be loaded via webpack dev server

// turbolinks comes from npm and is listed in webpack.client.base.config.js

//= require rails_startup
5 changes: 0 additions & 5 deletions app/assets/javascripts/rails_startup.js

This file was deleted.

19 changes: 18 additions & 1 deletion app/controllers/pages_controller.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
class PagesController < ApplicationController
include ReactOnRails::Controller
before_action :set_comments

def index
# NOTE: The below notes apply if you want to set the value of the props in the controller, as
# compared to he view. However, it's more convenient to use Jbuilder from the view. See
# compared to the view. However, it's more convenient to use Jbuilder from the view. See
# app/views/pages/index.html.erb:20
#
# <%= react_component('App', props: render(template: "/comments/index.json.jbuilder"),
Expand All @@ -20,10 +21,15 @@ def index
# respond_to do |format|
# format.html
# end

redux_store("routerCommentsStore", props: comments_json_string)
render_html
end

# Declaring no_router and simple to indicate we have views for them
def no_router
redux_store("commentsStore", props: comments_json_string)
render_html
end

def simple
Expand All @@ -34,4 +40,15 @@ def simple
def set_comments
@comments = Comment.all.order("id DESC")
end

def comments_json_string
render_to_string(template: "/comments/index.json.jbuilder",
locals: { comments: Comment.all }, format: :json)
end

def render_html
respond_to do |format|
format.html
end
end
end
30 changes: 5 additions & 25 deletions app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -20,36 +20,16 @@
<%= csrf_meta_tags %>
</head>
<body>
<nav class="navbar navbar-default" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="http://www.shakacode.com">ShakaCode</a>
</div>

<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><%= link_to "React Router Demo", root_path %></li>
<li><%= link_to "React Demo", no_router_path %></li>
<li><%= link_to "Simple React", simple_path %></li>
<li><%= link_to "Classic Rails", comments_path %></li>
<li><%= link_to "Source on Github", "https://github.com/shakacode/react-webpack-rails-tutorial" %></li>
<li><%= link_to "Tutorial Article", "http://www.railsonmaui.com/blog/2014/10/03/integrating-webpack-and-the-es6-transpiler-into-an-existing-rails-project/" %></li>
<li><%= link_to "Forum Discussion", "http://forum.shakacode.com/t/fast-rich-client-rails-development-with-webpack-and-the-es6-transpiler/82/22" %></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<%= react_component "NavigationBarApp" %>

<div class="container">
<%= yield %>
</div>

<!-- This is a placeholder for ReactOnRails to know where to render the store props for
client side hydration -->
<%= redux_store_hydration_data %>

</body>
</html>
4 changes: 2 additions & 2 deletions app/views/pages/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
</h3>
<%= render "header" %>
<%= react_component('RouterApp', props: render(template: "/comments/index.json.jbuilder"),
prerender: true, raise_on_prerender_error: true) %>
<%= react_component('RouterApp',
prerender: false, raise_on_prerender_error: true) %>
3 changes: 2 additions & 1 deletion app/views/pages/no_router.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<a href="https://github.com/shakacode/react_on_rails">react_on_rails gem</a>)</h2>
<%= render "header" %>

<!-- TODO: turn prerender back on -->
<%= react_component('App', props: render(template: "/comments/index.json.jbuilder"),
prerender: true) %>
prerender: false) %>
2 changes: 1 addition & 1 deletion app/views/pages/simple.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
</ul>
<hr/>

<%= react_component('SimpleCommentScreen', props: {}, prerender: false) %>
<%= react_component('SimpleCommentScreen', props: {}) %>
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default class CommentScreen extends BaseComponent {
{this._renderNotification()}
<div>
<CommentBox
pollInterval={10000}
pollInterval={60000}
data={data}
actions={actions}
ajaxCounter={data.get('ajaxCounter')}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React, {PropTypes} from 'react';

const CommentsCount = (props) => (
<li>
<a id='js-comment-count'
href='https://github.com/shakacode/react_on_rails/blob/master/README.md#multiple-react-components-on-a-page-with-one-store'>
Comments: {props.commentsCount}
</a>
</li>
);

CommentsCount.propTypes = {
commentsCount: PropTypes.number.isRequired,
};

export default CommentsCount;
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React, {PropTypes} from 'react';
import ReactOnRails from 'react-on-rails';
import classNames from 'classnames';
import CommentsCount from './CommentsCount';

const NavigationBar = (props) => {
console.log("ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ");
console.log("NavigationBar, props = ", props);
console.log("ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ");

const {commentsCount, pathname} = props;

return (
<nav className="navbar navbar-default" role="navigation">
<div className="container">
<div className="navbar-header">
<button
type="button"
className="navbar-toggle"
data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1"
>
<span className="sr-only">Toggle navigation</span>
<span className="icon-bar"/>
<span className="icon-bar"/>
<span className="icon-bar"/>
</button>
<a className="navbar-brand" href="http://www.shakacode.com">ShakaCode</a>
</div>
<div className="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul className="nav navbar-nav">
<li className={classNames({ active: (pathname === '/') })}><a href="/">React Router Demo</a></li>
<li className={classNames({ active: (pathname === '/no-router') })}><a href="/no-router">React Demo</a></li>
<li className={classNames({ active: (pathname === '/simple') })}><a href="/simple">Simple React</a></li>
<li className={classNames({ active: (pathname === '/comments') })}><a href="/comments">Classic Rails</a></li>
<li>
<a href={
'https://github.com/' +
'shakacode/react-webpack-rails-tutorial'
}>
Source on Github
</a>
</li>
<li>
<a href={
'http://www.railsonmaui.com/' +
'blog/2014/10/03/integrating' +
'-webpack-and-the-es6-transpiler' +
'-into-an-existing-rails-project/'
}>Tutorials</a>
</li>
<li>
<a href={
'http://forum.shakacode.com/' +
't/fast-rich-client-rails-development' +
'-with-webpack-and-the-es6-transpiler/82/22'
}>Forum</a>
</li>
{commentsCount && CommentsCount({ commentsCount })}
</ul>
</div>
</div>
</nav>
);
}

NavigationBar.propTypes = {
commentsCount: PropTypes.number.isRequired,
pathname: PropTypes.string.isRequired,
};

export default NavigationBar;
37 changes: 37 additions & 0 deletions client/app/bundles/comments/containers/NavigationBarContainer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import NavigationBar from '../components/NavigationBar/NavigationBar';
import * as commentsActionCreators from '../actions/commentsActionCreators';
import BaseComponent from 'libs/components/BaseComponent';

function stateToProps(state) {
// Which part of the Redux global state does our component want to receive as props?
if (state.$$commentsStore) {
return {
commentsCount: state.$$commentsStore.get('$$comments').size,
pathname: state.railsContext.pathname,
};
} else {
return { };
}
}

class NavigationBarContainer extends BaseComponent {
static propTypes = {
commentsCount: PropTypes.number.isRequired,
pathname: PropTypes.string.isRequired,
};

render() {
const { commentsCount, pathname } = this.props;

return (
<NavigationBar {...{commentsCount, pathname}} />
);
}
}

// Don't forget to actually use connect!
export default connect(stateToProps)(NavigationBarContainer);
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class RouterCommentsContainer extends BaseComponent {
const actions = bindActionCreators(commentsActionCreators, dispatch);
const locationState = this.props.location.state;


return (
<CommentScreen {...{ actions, data, locationState }} />
);
Expand Down
4 changes: 4 additions & 0 deletions client/app/bundles/comments/reducers/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import commentsReducer from './commentsReducer';
import railsContextReducer from './railsContextReducer';
import { $$initialState as $$commentsState } from './commentsReducer';
import { initialState as railsContextState } from './railsContextReducer';

export default {
$$commentsStore: commentsReducer,
railsContext: railsContextReducer,
};

export const initalStates = {
$$commentsState,
railsContextState,
};
5 changes: 5 additions & 0 deletions client/app/bundles/comments/reducers/railsContextReducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const initialState = {}

export default function railsContextReducer(state = initialState, action = null) {
return state;
}
6 changes: 3 additions & 3 deletions client/app/bundles/comments/startup/ClientApp.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from 'react';
import { Provider } from 'react-redux';

import createStore from '../store/commentsStore';
import NonRouterCommentsContainer from '../containers/NonRouterCommentsContainer';

export default props => {
const store = createStore(props);
export default (_props, _railsContext) => {
const store = ReactOnRails.getStore('commentsStore');

return (
<Provider store={store}>
<NonRouterCommentsContainer />
Expand Down
6 changes: 3 additions & 3 deletions client/app/bundles/comments/startup/ClientRouterApp.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React from 'react';
import { Provider } from 'react-redux';
import ReactOnRails from 'react-on-rails';
import { Router, browserHistory } from 'react-router';
import createStore from '../store/routerCommentsStore';
import routes from '../routes/routes';
import { syncHistoryWithStore } from 'react-router-redux';

export default (props, location) => {
const store = createStore(props);
export default (_props, _railsContext) => {
const store = ReactOnRails.getStore('routerCommentsStore');

// Create an enhanced history that syncs navigation events with the store
const history = syncHistoryWithStore(
Expand Down
35 changes: 35 additions & 0 deletions client/app/bundles/comments/startup/NavigationBarApp.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Top level component for client side.
// Compare this to the ./ServerApp.jsx file which is used for server side rendering.

import React from 'react';
import ReactOnRails from 'react-on-rails';
import NavigationBar from '../components/NavigationBar/NavigationBar';
import NavigationBarContainer from '../containers/NavigationBarContainer';
import { Provider } from 'react-redux';

/*
* Export a function that returns a ReactComponent, depending on a store named SharedReduxStore.
* This is used for the client rendering hook after the page html is rendered.
* React will see that the state is the same and not do anything.
*/
export default (_props, _railsContext) => {
// This is where we get the existing store.
const stores = ReactOnRails.stores();
let store;
store = ReactOnRails.getStore('routerCommentsStore', false);
if (!store) {
store = ReactOnRails.getStore('commentsStore', false);
}

if (!store) {
return (
<NavigationBar />
);
}

return (
<Provider store={store}>
<NavigationBarContainer />
</Provider>
);
};
8 changes: 4 additions & 4 deletions client/app/bundles/comments/startup/ServerApp.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import React from 'react';
import { Provider } from 'react-redux';

import createStore from '../store/commentsStore';
import NonRouterCommentsContainer from '../containers/NonRouterCommentsContainer';
import NavigationBarContainer from '../containers/NavigationBarContainer';

export default props => {
const store = createStore(props);
export default (_props, _rails_context) => {
const store = ReactOnRails.getStore('commentsStore');
return (
<Provider store={store}>
<NonRouterCommentsContainer />
<NavigationBarContainer />
</Provider>
);
};
3 changes: 1 addition & 2 deletions client/app/bundles/comments/startup/ServerRouterApp.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ import React from 'react';
import { Provider } from 'react-redux';
import { match, RouterContext } from 'react-router';

import createStore from '../store/commentsStore';
import routes from '../routes/routes';

export default (props, railsContext) => {
const store = createStore(props);
const store = ReactOnRails.getStore('routerCommentsStore');

let error;
let redirectLocation;
Expand Down

0 comments on commit 163f1ad

Please sign in to comment.