Quickstart
A quick dive into getting started with Lore
A quick dive into getting started with Lore
In this step we'll update our application to include support for WebSockets, so we can automatically display changes made by users without having to refresh the page.
You can view the finished code for this step by checking out the
websockets.1
branch of the completed project.
First install the lore-hook-websockets-sails
hook:
npm install lore-hook-websockets-sails sails.io.js@~0.13.8 socket.io-client@^1.4.8 --save
Then register the hook in your index.js
file:
// index.js
...
import websockets from 'lore-hook-websockets-sails';
lore.summon({
hooks: {
// ...
router,
websockets
}
});
Next add the config file that controls the hook:
// config/websockets.js
export default {
serverUrl: 'http://localhost:1337'
};
While the hook exposes more config options, the only one we need for this example is the location of the socket.io server, which runs on the same port as the Sails API.
Next, initialize the hook by subscribing to data for tweets
in the Master
component when the application mounts, and unsubscribe to the data when the application unmounts:
// src/components/Master.js
componentDidMount() {
lore.websockets.tweet.connect();
lore.websockets.tweet.subscribe();
},
componentWillUnmount() {
lore.websockets.tweet.unsubscribe();
},
// src/components/Master.js
componentDidMount() {
lore.websockets.tweet.connect();
lore.websockets.tweet.subscribe();
}
componentWillUnmount() {
lore.websockets.tweet.unsubscribe();
}
// src/components/Master.js
componentDidMount() {
lore.websockets.tweet.connect();
lore.websockets.tweet.subscribe();
}
componentWillUnmount() {
lore.websockets.tweet.unsubscribe();
}
That's it! With that change in place, the application will start listening for new data when it mounts, and tweets created by other users will immediately show up at the top of the feed, just like tweets you create yourself.
However, there's also a new bug, where the tweets you create yourself end up with a duplicate tweet that's faded out. We'll fix that in the next step.
If everything went well, your application should now look like this (the same as before) but now you can have multiple browser tabs open and the changes will synchronize across them.
Below is a list of files modified during this step.
export default {
serverUrl: 'http://localhost:1337'
};
/**
* This file kicks off the build process for the application. It also attaches
* the Lore singleton to the window, so you can access it from the command line
* in case you need to play with it or want to manually kick off actions or check
* the reducer state (through `lore.actions.xyz`, `lore.reducers.xyz`,
* `lore.models.xyz`, etc.)
*/
import lore from 'lore';
import _ from 'lodash';
// Import the styles for the loading screen. We're doing that here to make
// sure they get loaded regardless of the entry point for the application.
import './assets/css/loading-screen.css';
// Allows you to access your lore app globally as well as from within
// the console. Remove this line if you don't want to be able to do that.
window.lore = lore;
// Hooks
import auth from 'lore-hook-auth';
import actions from 'lore-hook-actions';
import bindActions from 'lore-hook-bind-actions';
import collections from 'lore-hook-collections';
import connections from 'lore-hook-connections';
import connect from 'lore-hook-connect';
import dialog from 'lore-hook-dialog-bootstrap';
import dialogs from 'lore-hook-dialogs-bootstrap';
import models from 'lore-hook-models';
import react from 'lore-hook-react';
import reducers from 'lore-hook-reducers';
import redux from 'lore-hook-redux';
import router from 'lore-hook-router';
import websockets from 'lore-hook-websockets-sails';
// Summon the app!
lore.summon({
hooks: {
auth,
actions,
bindActions,
collections,
connections,
connect,
dialog,
dialogs,
models,
react,
reducers,
redux: _.extend(redux, {
dependencies: ['reducers', 'auth']
}),
router,
websockets
}
});
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import { connect } from 'lore-hook-connect';
import PayloadStates from '../constants/PayloadStates';
import RemoveLoadingScreen from './RemoveLoadingScreen';
import '../../assets/css/main.css';
export default connect(function(getState, props) {
return {
user: getState('currentUser')
};
}, { subscribe: true })(
createReactClass({
displayName: 'Master',
propTypes: {
user: PropTypes.object.isRequired
},
childContextTypes: {
user: PropTypes.object
},
getChildContext() {
return {
user: this.props.user
};
},
componentDidMount() {
lore.websockets.tweet.connect();
lore.websockets.tweet.subscribe();
},
componentWillUnmount() {
lore.websockets.tweet.unsubscribe();
},
render() {
const { user } = this.props;
if (user.state === PayloadStates.FETCHING) {
return (
<div className="loader" />
);
}
if (user.state === PayloadStates.ERROR_FETCHING) {
return (
<div>
<RemoveLoadingScreen />
<h1 className="full-page-text">
Unauthorized
</h1>
</div>
);
}
return (
<div>
<RemoveLoadingScreen />
{React.cloneElement(this.props.children)}
</div>
);
}
})
);
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'lore-hook-connect';
import PayloadStates from '../constants/PayloadStates';
import RemoveLoadingScreen from './RemoveLoadingScreen';
import '../../assets/css/main.css';
class Master extends React.Component {
getChildContext() {
return {
user: this.props.user
};
}
componentDidMount() {
lore.websockets.tweet.connect();
lore.websockets.tweet.subscribe();
}
componentWillUnmount() {
lore.websockets.tweet.unsubscribe();
}
render() {
const { user } = this.props;
if (user.state === PayloadStates.FETCHING) {
return (
<div className="loader" />
);
}
if (user.state === PayloadStates.ERROR_FETCHING) {
return (
<div>
<RemoveLoadingScreen />
<h1 className="full-page-text">
Unauthorized
</h1>
</div>
);
}
return (
<div>
<RemoveLoadingScreen />
{React.cloneElement(this.props.children)}
</div>
);
}
}
Master.propTypes = {
user: PropTypes.object.isRequired
};
Master.childContextTypes = {
user: PropTypes.object
};
export default connect(function(getState, props) {
return {
user: getState('currentUser')
};
}, { subscribe: true })(Master);
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'lore-hook-connect';
import PayloadStates from '../constants/PayloadStates';
import RemoveLoadingScreen from './RemoveLoadingScreen';
import '../../assets/css/main.css';
@connect(function(getState, props) {
return {
user: getState('currentUser')
};
}, { subscribe: true })
class Master extends React.Component {
static propTypes = {
user: PropTypes.object.isRequired
};
static childContextTypes = {
user: PropTypes.object
};
getChildContext() {
return {
user: this.props.user
};
}
componentDidMount() {
lore.websockets.tweet.connect();
lore.websockets.tweet.subscribe();
}
componentWillUnmount() {
lore.websockets.tweet.unsubscribe();
}
render() {
const { user } = this.props;
if (user.state === PayloadStates.FETCHING) {
return (
<div className="loader" />
);
}
if (user.state === PayloadStates.ERROR_FETCHING) {
return (
<div>
<RemoveLoadingScreen />
<h1 className="full-page-text">
Unauthorized
</h1>
</div>
);
}
return (
<div>
<RemoveLoadingScreen />
{React.cloneElement(this.props.children)}
</div>
);
}
}
export default Master;
In the next step we'll learn how to fix the create bug.