Skip to content
This repository has been archived by the owner on May 19, 2020. It is now read-only.

Move render function out of route handlers #1155

Merged
merged 1 commit into from
Jul 11, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions static_src/actions/router_actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import AppDispatcher from '../dispatcher.js';
import { routerActionTypes } from '../constants';

const routerActions = {
navigate(component, props) {
AppDispatcher.handleViewAction({
type: routerActionTypes.NAVIGATE,
data: {
component,
props
}
});
}
};

export default routerActions;
2 changes: 1 addition & 1 deletion static_src/components/header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default class Header extends React.Component {
<header className={ this.styler('header', 'header-no_sidebar', 'test-header') }>
<div className={ this.styler('header-wrap') }>
<div className={ this.styler('header-title') }>
<a href="/" className={ this.styler('logo') } title="Home">
<a href="/#/" className={ this.styler('logo') } title="Home">
<svg className={ this.styler('logo-img') }>
<use
xlinkHref={ this.getImagePath('logo-dashboard') }
Expand Down
1 change: 0 additions & 1 deletion static_src/components/main_container.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ function stateSetter() {

class App extends React.Component {
constructor(props) {
console.log(style, overrideStyle)
super(props);
this.styler = createStyler(style, overrideStyle);
this.state = stateSetter();;
Expand Down
6 changes: 6 additions & 0 deletions static_src/components/not_found.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';

const NotFound = () =>
<h1>Not Found</h1>;

export default NotFound;
40 changes: 40 additions & 0 deletions static_src/components/router/route_provider.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import RouterStore from '../../stores/router_store.js';
import Loading from '../loading.jsx';

class RouteProvider extends React.Component {
constructor() {
super();

this.state = RouterStore.component;

this.onChange = this.onChange.bind(this);
}

componentDidMount() {
RouterStore.addChangeListener(this.onChange);
}

shouldComponentUpdate(nextProps, nextState) {
if (this.state === nextState) {
return false;
}

return true;
}

componentWillUnmount() {
RouterStore.removeChangeListener(this.onChange);
}

onChange() {
this.setState({ ...RouterStore.component });
}

render() {
const { component: Component, props } = this.state;
return Component ? <Component { ...props } /> : <Loading />;
}
}

export default RouteProvider;
5 changes: 5 additions & 0 deletions static_src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,10 @@ const routeActionTypes = keymirror({
ROUTE_ERROR: null
});

const routerActionTypes = keymirror({
NAVIGATE: null
});

const domainActionTypes = keymirror({
DOMAIN_FETCH: null,
DOMAIN_RECEIVED: null
Expand Down Expand Up @@ -366,6 +370,7 @@ export {
pageActionTypes,
quotaActionTypes,
routeActionTypes,
routerActionTypes,
spaceActionTypes,
serviceActionTypes,
userActionTypes
Expand Down
48 changes: 35 additions & 13 deletions static_src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,44 @@ import './css/main.css';
import './img/dashboard-uaa-icon.jpg';
import 'cloudgov-style/img/favicon.ico';

import React from 'react';
import ReactDOM from 'react-dom';

import axios from 'axios';
import { Router } from 'director';

import { trackPageView } from './util/analytics.js';
import routes, { checkAuth, clearErrors, notFound } from './routes';

const meta = document.querySelector('meta[name="gorilla.csrf.Token"]');
if (meta) {
axios.defaults.headers.common['X-CSRF-Token'] = meta.content;
}

const router = new Router(routes);
router.configure({
async: true,
before: [clearErrors, checkAuth],
notfound: notFound,
on: () => trackPageView(window.location.hash)
});
router.init('/');
import MainContainer from './components/main_container.jsx';
import RouteProvider from './components/router/route_provider.jsx';

const initCSRFHeader = metaTag => {
if (metaTag) {
axios.defaults.headers.common['X-CSRF-Token'] = metaTag.content;
}
};

const cRouter = {
run(routeConfig, renderEl) {
const router = new Router(routeConfig);
router.configure({
async: true,
before: [clearErrors, checkAuth],
notfound: notFound,
on: () => {
trackPageView(window.location.hash);
}
});

ReactDOM.render(
<MainContainer>
<RouteProvider />
</MainContainer>, renderEl);

router.init('/');
}
};

initCSRFHeader(document.querySelector('meta[name="gorilla.csrf.Token"]'));
cRouter.run(routes, document.querySelector('.js-app'));
48 changes: 13 additions & 35 deletions static_src/routes.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import React from 'react';
import ReactDOM from 'react-dom';

import activityActions from './actions/activity_actions.js';
import AppContainer from './components/app_container.jsx';
import appActions from './actions/app_actions.js';
Expand All @@ -10,7 +7,7 @@ import Loading from './components/loading.jsx';
import Login from './components/login.jsx';
import loginActions from './actions/login_actions';
import LoginStore from './stores/login_store';
import MainContainer from './components/main_container.jsx';
import NotFound from './components/not_found.jsx';
import orgActions from './actions/org_actions.js';
import Overview from './components/overview_container.jsx';
import OrgContainer from './components/org_container.jsx';
Expand All @@ -24,15 +21,12 @@ import { appHealth } from './util/health.js';
import { entityHealth } from './constants.js';
import windowUtil from './util/window';
import userActions from './actions/user_actions.js';

// TODO this is hard to stub since we query it at module load time. It should
// be passed in or something.
const mainEl = document.querySelector('.js-app');
import routerActions from './actions/router_actions.js';

const MAX_OVERVIEW_SPACES = 10;

export function login(next) {
ReactDOM.render(<MainContainer><Login /></MainContainer>, mainEl);
routerActions.navigate(Login);
next();
}

Expand All @@ -56,11 +50,7 @@ export function overview(next) {
return Promise.all(fetches);
})
.then(pageActions.loadSuccess, pageActions.loadError);

ReactDOM.render(<MainContainer>
<Overview />
</MainContainer>, mainEl);

routerActions.navigate(Overview);
next();
}

Expand All @@ -75,11 +65,7 @@ export function org(orgGuid, next) {
userActions.changeCurrentlyViewedType('org_users');
userActions.fetchOrgUsers(orgGuid);
userActions.fetchOrgUserRoles(orgGuid);
ReactDOM.render(
<MainContainer>
<OrgContainer />
</MainContainer>, mainEl);

routerActions.navigate(OrgContainer);
next();
}

Expand All @@ -97,13 +83,7 @@ export function space(orgGuid, spaceGuid, next) {
userActions.fetchSpaceUserRoles(spaceGuid);
orgActions.fetch(orgGuid);
serviceActions.fetchAllServices(orgGuid);

ReactDOM.render(
<MainContainer>
<SpaceContainer
currentPage="apps"
/>
</MainContainer>, mainEl);
routerActions.navigate(SpaceContainer, { currentPage: 'apps' });
next();
}

Expand All @@ -126,10 +106,7 @@ export function app(orgGuid, spaceGuid, appGuid, next) {
routeActions.fetchRoutesForApp(appGuid);
serviceActions.fetchAllInstances(spaceGuid);
serviceActions.fetchServiceBindings();
ReactDOM.render(
<MainContainer>
<AppContainer />
</MainContainer>, mainEl);
routerActions.navigate(AppContainer);
next();
}

Expand Down Expand Up @@ -169,9 +146,11 @@ export function checkAuth(...args) {
// Just in case something goes wrong, don't leave the user hanging. Show
// a delayed loading indicator to give them a hint. Hopefully the
// redirect is quick and they never see the loader.
ReactDOM.render(
<Loading text="Redirecting to login" loadingDelayMS={ 3000 } style="inline" />
, mainEl);
routerActions.navigate(Loading, {
text: 'Redirecting to login',
loadingDelayMS: 3000,
style: 'inline'
});

// Stop the routing
next(false);
Expand Down Expand Up @@ -199,8 +178,7 @@ export function notFound(next) {
orgActions.changeCurrentOrg();
spaceActions.changeCurrentSpace();
appActions.changeCurrentApp();

ReactDOM.render(<h1>Not Found</h1>, mainEl);
routerActions.navigate(NotFound);
next();
}

Expand Down
30 changes: 30 additions & 0 deletions static_src/stores/router_store.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import BaseStore from './base_store.js';
import { routerActionTypes } from '../constants.js';

class RouterStore extends BaseStore {
constructor() {
super();

this.routeComponent = {};
this.subscribe(() => this.registerToActions.bind(this));
}

registerToActions(action) {
const { type, data } = action;

switch (type) {
case routerActionTypes.NAVIGATE:
this.routeComponent = Object.assign({}, { ...data });
this.emitChange();
break;
default:
break;
}
}

get component() {
return this.routeComponent;
}
}

export default new RouterStore();
30 changes: 30 additions & 0 deletions static_src/test/unit/actions/router_actions.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import '../../global_setup';
import React from 'react';
import { assertAction, setupViewSpy } from '../helpers';
import routerActions from '../../../actions/router_actions';
import { routerActionTypes } from '../../../constants';

describe('routerActions', () => {
let sandbox;

beforeEach(() => {
sandbox = sinon.sandbox.create();
});

afterEach(() => {
sandbox.restore();
});

describe('navigate()', () => {
it('dispatches `NAVIGATE` action and passes a component and props', () => {
const props = { some: 'data' };
const component = () => <div></div>;
const expected = { component, props };
const spy = setupViewSpy(sandbox);

routerActions.navigate(component, props);

assertAction(spy, routerActionTypes.NAVIGATE, expected);
});
});
});
29 changes: 29 additions & 0 deletions static_src/test/unit/components/router/route_provider.spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import '../../../global_setup';
import React from 'react';
import RouteProvider from '../../../../components/router/route_provider.jsx';
import Loading from '../../../../components/loading.jsx';
import { shallow } from 'enzyme';
import RouterStore from '../../../../stores/router_store';

describe('<RouteProvider />', () => {
beforeEach(() => {
RouterStore.routeComponent = {};
});

it('sets a default state from the RouteStore', () => {
const component = () => <div></div>;
const props = { some: 'data' };
RouterStore.routeComponent = Object.assign({}, { component, props });
const wrapper = shallow(<RouteProvider />);

expect(wrapper.state().component).toBe(component);
expect(wrapper.state().props.some).toBe('data');
expect(wrapper.find(component).length).toBe(1);
});

it('renders a loading component as a fallback', () => {
const wrapper = shallow(<RouteProvider />);

expect(wrapper.find(Loading).length).toBe(1);
});
});
3 changes: 2 additions & 1 deletion static_src/test/unit/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ export function assertAction(spy, type, params) {
let actionInfo = spy.getCall(0).args[0];
expect(actionInfo.type).toEqual(type);
for (let param in params) {
expect(actionInfo[param]).toEqual(params[param]);
const datum = 'data' in actionInfo ? actionInfo.data[param] : actionInfo[param];
expect(datum).toEqual(params[param]);
}
}

Expand Down
6 changes: 3 additions & 3 deletions static_src/test/unit/routes.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

import '../global_setup.js';
import ReactDOM from 'react-dom';
import routerActions from '../../actions/router_actions';
import errorActions from '../../actions/error_actions';
import loginActions from '../../actions/login_actions';
import LoginStore from '../../stores/login_store';
Expand Down Expand Up @@ -126,7 +126,7 @@ describe('routes', function () {

beforeEach(function (done) {
next = sandbox.spy(done);
sandbox.stub(ReactDOM, 'render');
sandbox.stub(routerActions, 'navigate');
sandbox.stub(windowUtil, 'redirect');
loginActions.fetchStatus.returns(Promise.resolve({ status: 'unauthorized' }));

Expand All @@ -142,7 +142,7 @@ describe('routes', function () {
});

it('renders a loader', function () {
expect(ReactDOM.render).toHaveBeenCalledOnce();
expect(routerActions.navigate).toHaveBeenCalledOnce();
});

it('does not fetch page data', function () {
Expand Down
Loading