diff --git a/src/lib/hash-parser-hoc.jsx b/src/lib/hash-parser-hoc.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..cf3953824662b2219f04a5587c60ed7f60b19e6a
--- /dev/null
+++ b/src/lib/hash-parser-hoc.jsx
@@ -0,0 +1,49 @@
+import React from 'react';
+
+/* Higher Order Component to get the project id from location.hash
+ * @param {React.Component} WrappedComponent component to receive projectData prop
+ * @returns {React.Component} component with project loading behavior
+ */
+const HashParserHOC = function (WrappedComponent) {
+    class HashParserComponent extends React.Component {
+        constructor (props) {
+            super(props);
+            this.fetchProjectId = this.fetchProjectId.bind(this);
+            this.updateProject = this.updateProject.bind(this);
+            this.state = {
+                projectId: null
+            };
+        }
+        componentDidMount () {
+            window.addEventListener('hashchange', this.updateProject);
+            this.updateProject();
+        }
+        componentWillUnmount () {
+            window.removeEventListener('hashchange', this.updateProject);
+        }
+        fetchProjectId () {
+            return window.location.hash.substring(1);
+        }
+        updateProject () {
+            let projectId = this.fetchProjectId();
+            if (projectId !== this.state.projectId) {
+                if (projectId.length < 1) projectId = 0;
+                this.setState({projectId: projectId});
+            }
+        }
+        render () {
+            return (
+                <WrappedComponent
+                    projectId={this.state.projectId}
+                    {...this.props}
+                />
+            );
+        }
+    }
+
+    return HashParserComponent;
+};
+
+export {
+    HashParserHOC as default
+};
diff --git a/src/lib/project-loader-hoc.jsx b/src/lib/project-loader-hoc.jsx
index cad9367802759f35cec601e4eb89a4ede989503b..b513be019eeca89ecee09d5eede9830e30f19355 100644
--- a/src/lib/project-loader-hoc.jsx
+++ b/src/lib/project-loader-hoc.jsx
@@ -5,9 +5,8 @@ import analytics from './analytics';
 import log from './log';
 import storage from './storage';
 
-/* Higher Order Component to provide behavior for loading projects by id from
- * the window's hash (#this part in the url) or by projectId prop passed in from
- * the parent (i.e. scratch-www)
+/* Higher Order Component to provide behavior for loading projects by id. If
+ * there's no id, the default project is loaded.
  * @param {React.Component} WrappedComponent component to receive projectData prop
  * @returns {React.Component} component with project loading behavior
  */
@@ -15,52 +14,40 @@ const ProjectLoaderHOC = function (WrappedComponent) {
     class ProjectLoaderComponent extends React.Component {
         constructor (props) {
             super(props);
-            this.fetchProjectId = this.fetchProjectId.bind(this);
             this.updateProject = this.updateProject.bind(this);
             this.state = {
-                projectId: null,
                 projectData: null,
                 fetchingProject: false
             };
         }
         componentDidMount () {
-            window.addEventListener('hashchange', this.updateProject);
-            this.updateProject();
+            this.updateProject(this.props.projectId);
         }
-        componentWillUpdate (nextProps, nextState) {
-            if (this.state.projectId !== nextState.projectId) {
+        componentWillUpdate (nextProps) {
+            if (this.props.projectId !== nextProps.projectId) {
                 this.setState({fetchingProject: true}, () => {
-                    storage
-                        .load(storage.AssetType.Project, this.state.projectId, storage.DataFormat.JSON)
-                        .then(projectAsset => projectAsset && this.setState({
-                            projectData: projectAsset.data,
-                            fetchingProject: false
-                        }))
-                        .catch(err => log.error(err));
+                    this.updateProject(nextProps.projectId);
                 });
             }
         }
-        componentWillUnmount () {
-            window.removeEventListener('hashchange', this.updateProject);
-        }
-        fetchProjectId () {
-            return window.location.hash.substring(1);
-        }
-        updateProject () {
-            let projectId = this.props.projectId || this.fetchProjectId();
-            if (projectId !== this.state.projectId) {
-                if (projectId.length < 1) projectId = 0;
-                this.setState({projectId: projectId});
-
-                if (projectId !== 0) {
-                    analytics.event({
-                        category: 'project',
-                        action: 'Load Project',
-                        value: projectId,
-                        nonInteraction: true
-                    });
-                }
-            }
+        updateProject (projectId) {
+            storage
+                .load(storage.AssetType.Project, projectId, storage.DataFormat.JSON)
+                .then(projectAsset => projectAsset && this.setState({
+                    projectData: projectAsset.data,
+                    fetchingProject: false
+                }))
+                .then(() => {
+                    if (projectId !== 0) {
+                        analytics.event({
+                            category: 'project',
+                            action: 'Load Project',
+                            value: projectId,
+                            nonInteraction: true
+                        });
+                    }
+                })
+                .catch(err => log.error(err));
         }
         render () {
             const {
@@ -78,7 +65,10 @@ const ProjectLoaderHOC = function (WrappedComponent) {
         }
     }
     ProjectLoaderComponent.propTypes = {
-        projectId: PropTypes.string
+        projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
+    };
+    ProjectLoaderComponent.defaultProps = {
+        projectId: 0
     };
 
     return ProjectLoaderComponent;
diff --git a/src/playground/blocks-only.jsx b/src/playground/blocks-only.jsx
index af330d39d81c4de0a59e82c26a53275aa6e4b370..9a925424cc2d1d5c1ea4d9b38e855f95ff3a3f3e 100644
--- a/src/playground/blocks-only.jsx
+++ b/src/playground/blocks-only.jsx
@@ -5,7 +5,7 @@ import {connect} from 'react-redux';
 import Controls from '../containers/controls.jsx';
 import Blocks from '../containers/blocks.jsx';
 import GUI from '../containers/gui.jsx';
-import ProjectLoaderHOC from '../lib/project-loader-hoc.jsx';
+import HashParserHOC from '../lib/hash-parser-hoc.jsx';
 
 import styles from './blocks-only.css';
 
@@ -26,7 +26,7 @@ const BlocksOnly = props => (
     </GUI>
 );
 
-const App = ProjectLoaderHOC(BlocksOnly);
+const App = HashParserHOC(BlocksOnly);
 
 const appTarget = document.createElement('div');
 document.body.appendChild(appTarget);
diff --git a/src/playground/compatibility-testing.jsx b/src/playground/compatibility-testing.jsx
index b55a34c4ea6b0b70563a9f189284ae82f0019d30..a1b6fba5cea1963787f616f6932e2bda15aaf957 100644
--- a/src/playground/compatibility-testing.jsx
+++ b/src/playground/compatibility-testing.jsx
@@ -6,7 +6,7 @@ import Controls from '../containers/controls.jsx';
 import Stage from '../containers/stage.jsx';
 import Box from '../components/box/box.jsx';
 import GUI from '../containers/gui.jsx';
-import ProjectLoaderHOC from '../lib/project-loader-hoc.jsx';
+import HashParserHOC from '../lib/hash-parser-hoc.jsx';
 
 const mapStateToProps = state => ({vm: state.vm});
 
@@ -71,7 +71,7 @@ class Player extends React.Component {
     }
 }
 
-const App = ProjectLoaderHOC(Player);
+const App = HashParserHOC(Player);
 
 const appTarget = document.createElement('div');
 document.body.appendChild(appTarget);
diff --git a/src/playground/index.jsx b/src/playground/index.jsx
index 7d138947034ac47ff0b4657b2611f2102f7e2e46..60e018fb6501fa7e865916dd4920595390b7a780 100644
--- a/src/playground/index.jsx
+++ b/src/playground/index.jsx
@@ -4,6 +4,7 @@ import ReactDOM from 'react-dom';
 
 import analytics from '../lib/analytics';
 import GUI from '../containers/gui.jsx';
+import HashParserHOC from '../lib/hash-parser-hoc.jsx';
 
 import styles from './index.css';
 
@@ -20,5 +21,6 @@ appTarget.className = styles.app;
 document.body.appendChild(appTarget);
 
 GUI.setAppElement(appTarget);
+const WrappedGui = HashParserHOC(GUI);
 
-ReactDOM.render(<GUI />, appTarget);
+ReactDOM.render(<WrappedGui />, appTarget);
diff --git a/src/playground/player.jsx b/src/playground/player.jsx
index 25dfc9bfcd13bb8c95457c045be9c672b77f4ef0..4ecae4c0c13af54df9cf36b25475fe4e027dfe53 100644
--- a/src/playground/player.jsx
+++ b/src/playground/player.jsx
@@ -3,6 +3,8 @@ import ReactDOM from 'react-dom';
 
 import Box from '../components/box/box.jsx';
 import GUI from '../containers/gui.jsx';
+import HashParserHOC from '../lib/hash-parser-hoc.jsx';
+const WrappedGui = HashParserHOC(GUI);
 
 if (process.env.NODE_ENV === 'production' && typeof window === 'object') {
     // Warn before navigating away
@@ -12,7 +14,7 @@ if (process.env.NODE_ENV === 'production' && typeof window === 'object') {
 import styles from './player.css';
 const Player = () => (
     <Box className={styles.stageOnly}>
-        <GUI
+        <WrappedGui
             isPlayerOnly
             isFullScreen={false}
         />
diff --git a/test/unit/util/hash-project-loader-hoc.test.jsx b/test/unit/util/hash-project-loader-hoc.test.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..9ab9af432accc09ae0ed7781b2642386ed056f59
--- /dev/null
+++ b/test/unit/util/hash-project-loader-hoc.test.jsx
@@ -0,0 +1,64 @@
+import React from 'react';
+import ProjectLoaderHOC from '../../../src/lib/project-loader-hoc.jsx';
+import HashParserHOC from '../../../src/lib/hash-parser-hoc.jsx';
+import storage from '../../../src/lib/storage';
+import {mount} from 'enzyme';
+
+jest.mock('react-ga');
+
+describe('Hash/ProjectLoaderHOC', () => {
+    test('when there is no project data, it renders null', () => {
+        const Component = ({projectData}) => <div>{projectData}</div>;
+        const WrappedComponent = HashParserHOC(ProjectLoaderHOC(Component));
+        window.location.hash = '#winning';
+        const originalLoad = storage.load;
+        storage.load = jest.fn(() => Promise.resolve(null));
+        const mounted = mount(<WrappedComponent />);
+        storage.load = originalLoad;
+        window.location.hash = '';
+        const mountedDiv = mounted.find('div');
+        expect(mountedDiv.exists()).toEqual(false);
+    });
+
+    test('when there is no hash, it loads the default project', () => {
+        const Component = ({projectData}) => <div>{projectData}</div>;
+        const WrappedComponent = HashParserHOC(ProjectLoaderHOC(Component));
+        window.location.hash = '';
+        const originalLoad = storage.load;
+        storage.load = jest.fn((type, id) => Promise.resolve(id));
+        const mounted = mount(<WrappedComponent />);
+        expect(mounted.state().projectId).toEqual(0);
+        expect(storage.load).toHaveBeenCalledWith(
+            storage.AssetType.Project, 0, storage.DataFormat.JSON
+        );
+        storage.load = originalLoad;
+    });
+
+    test('when there is a hash, it tries to load that project', () => {
+        const Component = ({projectData}) => <div>{projectData}</div>;
+        const WrappedComponent = HashParserHOC(ProjectLoaderHOC(Component));
+        window.location.hash = '#winning';
+        const originalLoad = storage.load;
+        storage.load = jest.fn((type, id) => Promise.resolve({data: id}));
+        const mounted = mount(<WrappedComponent />);
+        expect(mounted.state().projectId).toEqual('winning');
+        expect(storage.load).toHaveBeenLastCalledWith(
+            storage.AssetType.Project, 'winning', storage.DataFormat.JSON
+        );
+        storage.load = originalLoad;
+    });
+
+    test('when hash change happens, the project data state is changed', () => {
+        const Component = ({projectData}) => <div>{projectData}</div>;
+        const WrappedComponent = HashParserHOC(ProjectLoaderHOC(Component));
+        window.location.hash = '';
+        const mounted = mount(<WrappedComponent />);
+        expect(mounted.state().projectId).toEqual(0);
+        const originalLoad = storage.load;
+        storage.load = jest.fn((type, id) => Promise.resolve({data: id}));
+        window.location.hash = '#winning';
+        mounted.instance().updateProject();
+        expect(mounted.state().projectId).toEqual('winning');
+        storage.load = originalLoad;
+    });
+});
diff --git a/test/unit/util/project-loader-hoc.test.jsx b/test/unit/util/project-loader-hoc.test.jsx
index 5acde098e53b8630f1a9b2dbc74177faadcb70d8..30278892f4c1b87a09a983d93f196ee3f6d37525 100644
--- a/test/unit/util/project-loader-hoc.test.jsx
+++ b/test/unit/util/project-loader-hoc.test.jsx
@@ -6,58 +6,31 @@ import {mount} from 'enzyme';
 jest.mock('react-ga');
 
 describe('ProjectLoaderHOC', () => {
-    test('when there is no project data, it renders null', () => {
-        const Component = ({projectData}) => <div>{projectData}</div>;
-        const WrappedComponent = ProjectLoaderHOC(Component);
-        window.location.hash = '#winning';
-        const originalLoad = storage.load;
-        storage.load = jest.fn(() => Promise.resolve(null));
-        const mounted = mount(<WrappedComponent />);
-        storage.load = originalLoad;
-        window.location.hash = '';
-        const mountedDiv = mounted.find('div');
-        expect(mountedDiv.exists()).toEqual(false);
-    });
 
-    test('when there is no hash, it loads the default project', () => {
+    test('when there is no id, it loads (default) project id 0', () => {
         const Component = ({projectData}) => <div>{projectData}</div>;
         const WrappedComponent = ProjectLoaderHOC(Component);
-        window.location.hash = '';
         const originalLoad = storage.load;
         storage.load = jest.fn((type, id) => Promise.resolve(id));
         const mounted = mount(<WrappedComponent />);
-        expect(mounted.state().projectId).toEqual(0);
+        expect(mounted.props().projectId).toEqual(0);
         expect(storage.load).toHaveBeenCalledWith(
             storage.AssetType.Project, 0, storage.DataFormat.JSON
         );
         storage.load = originalLoad;
     });
 
-    test('when there is a hash, it tries to load that project', () => {
+    test('when there is an id, it tries to load that project', () => {
         const Component = ({projectData}) => <div>{projectData}</div>;
         const WrappedComponent = ProjectLoaderHOC(Component);
-        window.location.hash = '#winning';
         const originalLoad = storage.load;
         storage.load = jest.fn((type, id) => Promise.resolve({data: id}));
-        const mounted = mount(<WrappedComponent />);
-        expect(mounted.state().projectId).toEqual('winning');
+        const mounted = mount(<WrappedComponent projectId="100" />);
+        expect(mounted.props().projectId).toEqual('100');
         expect(storage.load).toHaveBeenLastCalledWith(
-            storage.AssetType.Project, 'winning', storage.DataFormat.JSON
+            storage.AssetType.Project, '100', storage.DataFormat.JSON
         );
         storage.load = originalLoad;
     });
 
-    test('when hash change happens, the project data state is changed', () => {
-        const Component = ({projectData}) => <div>{projectData}</div>;
-        const WrappedComponent = ProjectLoaderHOC(Component);
-        window.location.hash = '';
-        const mounted = mount(<WrappedComponent />);
-        expect(mounted.state().projectId).toEqual(0);
-        const originalLoad = storage.load;
-        storage.load = jest.fn((type, id) => Promise.resolve({data: id}));
-        window.location.hash = '#winning';
-        mounted.instance().updateProject();
-        expect(mounted.state().projectId).toEqual('winning');
-        storage.load = originalLoad;
-    });
 });