Quickstart
A quick dive into getting started with Lore
A quick dive into getting started with Lore
In this step we're going to introduce a version of lore-hook-dialog
that is tailored for mounting, showing, and dismissing Bootstrap dialogs.
You can view the finished code for this step by checking out the
dialogs.4
branch of the completed project.
The dialog we just created is responsible for showing and dismissing itself. But in a real application, you may have dozens of dialogs for creating, updating and deleting content. And if your application uses a UI library like Bootstrap, each of those dialogs will likely have the exact same code for doing so.
So in this step, we're just going to embrace that, and we're going to introduce a version of lore-hook-dialog
that has been customized to understand how to show and dismiss Bootstrap dialogs. This package is called lore-hook-dialog-bootstrap
.
Run this command to install the package:
npm install lore-hook-dialog-bootstrap --save
Next open index.js
and replace the lore-hook-dialog
hook with lore-hook-dialog-bootstrap
like this:
// index.js
...
import dialog from 'lore-hook-dialog-bootstrap';
...
lore.summon({
hooks: {
...
dialog,
...
}
});
With that change in place, the application will still work, but you'll notice that now when you launch the dialog, it now has a super dark backdrop that it didn't before.
This is happening because we're now creating two backdrops. Previously, our dialog needed to include code for showing and hiding itself, and it's this code (shown below) that also generates the backdrop:
// src/components/CreateTweetDialog.js
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import _ from 'lodash';
export default createReactClass({
displayName: 'CreateTweetDialog',
componentDidMount() {
this.show();
},
show() {
const modal = this.refs.modal;
$(modal).modal('show');
},
dismiss() {
const modal = this.refs.modal;
$(modal).modal('hide');
},
render() {
const { data } = this.state;
return (
<div ref="modal" className="modal fade">
{/* ...your dialog renders here... */}
</div>
);
}
});
// src/components/CreateTweetDialog.js
import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
class CreateTweetDialog extends React.Component {
constructor(props) {
super(props);
// bind custom methods
this.show = this.show.bind(this);
this.dismiss = this.dismiss.bind(this);
}
componentDidMount() {
this.show();
}
show() {
const modal = this.refs.modal;
$(modal).modal('show');
}
dismiss() {
const modal = this.refs.modal;
$(modal).modal('hide');
}
render() {
const { data } = this.state;
return (
<div ref="modal" className="modal fade">
{/* ...your dialog renders here... */}
</div>
);
}
}
// src/components/CreateTweetDialog.js
import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
class CreateTweetDialog extends React.Component {
constructor(props) {
super(props);
// bind custom methods
this.show = this.show.bind(this);
this.dismiss = this.dismiss.bind(this);
}
componentDidMount() {
this.show();
}
show() {
const modal = this.refs.modal;
$(modal).modal('show');
}
dismiss() {
const modal = this.refs.modal;
$(modal).modal('hide');
}
render() {
const { data } = this.state;
return (
<div ref="modal" className="modal fade">
{/* ...your dialog renders here... */}
</div>
);
}
}
Since the new lore.dialog.show()
method automatically wraps each dialog with this code for us, we can now remove this boilerplate from our dialog.
Open the CreateTweetDialog
and update it to look like this:
// src/components/CreateTweetDialog.js
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import _ from 'lodash';
export default createReactClass({
displayName: 'CreateTweetDialog',
propTypes: {
onCancel: PropTypes.func
},
getInitialState() {
return {
data: {
text: ''
}
};
},
request(data) {
lore.actions.tweet.create(data);
},
onSubmit() {
const { data } = this.state;
this.request(data);
this.dismiss();
},
dismiss() {
this.props.onCancel();
},
onChange(name, value) {
const nextData = _.merge({}, this.state.data);
nextData[name] = value;
this.setState({
data: nextData
});
},
render() {
const { data } = this.state;
return (
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<button type="button" className="close" onClick={this.dismiss}>
<span>×</span>
</button>
<h4 className="modal-title">
Create Tweet
</h4>
</div>
<div className="modal-body">
<div className="row">
<div className="col-md-12">
<div className="form-group">
<label>Message</label>
<textarea
className="form-control"
rows="3"
value={data.text}
placeholder="What's happening?"
onChange={(event) => {
this.onChange('text', event.target.value)
}}
/>
</div>
</div>
</div>
</div>
<div className="modal-footer">
<div className="row">
<div className="col-md-12">
<button
type="button"
className="btn btn-default"
onClick={this.dismiss}
>
Cancel
</button>
<button
type="button"
className="btn btn-primary"
disabled={!data.text}
onClick={this.onSubmit}
>
Create
</button>
</div>
</div>
</div>
</div>
</div>
);
}
});
// src/components/CreateTweetDialog.js
import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
class CreateTweetDialog extends React.Component {
constructor(props) {
super(props);
// set initial state
this.state = {
data: {
text: ''
}
};
// bind custom methods
this.request = this.request.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.onChange = this.onChange.bind(this);
}
request(data) {
lore.actions.tweet.create(data);
}
onSubmit() {
const { data } = this.state;
this.request(data);
this.dismiss();
}
onChange(name, value) {
const nextData = _.merge({}, this.state.data);
nextData[name] = value;
this.setState({
data: nextData
});
}
render() {
const { data } = this.state;
return (
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<button type="button" className="close" onClick={this.dismiss}>
<span>×</span>
</button>
<h4 className="modal-title">
Create Tweet
</h4>
</div>
<div className="modal-body">
<div className="row">
<div className="col-md-12">
<div className="form-group">
<label>Message</label>
<textarea
className="form-control"
rows="3"
value={data.text}
placeholder="What's happening?"
onChange={(event) => {
this.onChange('text', event.target.value)
}}
/>
</div>
</div>
</div>
</div>
<div className="modal-footer">
<div className="row">
<div className="col-md-12">
<button
type="button"
className="btn btn-default"
onClick={this.dismiss}
>
Cancel
</button>
<button
type="button"
className="btn btn-primary"
disabled={!data.text}
onClick={this.onSubmit}
>
Create
</button>
</div>
</div>
</div>
</div>
</div>
);
}
}
CreateTweetDialog.propTypes = {
title: PropTypes.node,
description: PropTypes.node
};
export default CreateTweetDialog;
// src/components/CreateTweetDialog.js
import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
class CreateTweetDialog extends React.Component {
static propTypes = {
title: PropTypes.node,
description: PropTypes.node
};
constructor(props) {
super(props);
// set initial state
this.state = {
data: {
text: ''
}
};
// bind custom methods
this.request = this.request.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.onChange = this.onChange.bind(this);
}
request(data) {
lore.actions.tweet.create(data);
}
onSubmit() {
const { data } = this.state;
this.request(data);
this.dismiss();
}
onChange(name, value) {
const nextData = _.merge({}, this.state.data);
nextData[name] = value;
this.setState({
data: nextData
});
}
render() {
const { data } = this.state;
return (
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<button type="button" className="close" onClick={this.dismiss}>
<span>×</span>
</button>
<h4 className="modal-title">
Create Tweet
</h4>
</div>
<div className="modal-body">
<div className="row">
<div className="col-md-12">
<div className="form-group">
<label>Message</label>
<textarea
className="form-control"
rows="3"
value={data.text}
placeholder="What's happening?"
onChange={(event) => {
this.onChange('text', event.target.value)
}}
/>
</div>
</div>
</div>
</div>
<div className="modal-footer">
<div className="row">
<div className="col-md-12">
<button
type="button"
className="btn btn-default"
onClick={this.dismiss}
>
Cancel
</button>
<button
type="button"
className="btn btn-primary"
disabled={!data.text}
onClick={this.onSubmit}
>
Create
</button>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default CreateTweetDialog;
With that change in place, if you launch your dialog again, it will look and behave like we expect, and will once again have only a single backdrop.
If everything went well, your application should now look like this.
Below is a list of files modified during this step.
/**
* 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 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';
// Summon the app!
lore.summon({
hooks: {
auth,
actions,
bindActions,
collections,
connections,
connect,
dialog,
models,
react,
reducers,
redux: _.extend(redux, {
dependencies: ['reducers', 'auth']
}),
router
}
});
// src/components/CreateTweetDialog.js
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import _ from 'lodash';
export default createReactClass({
displayName: 'CreateTweetDialog',
propTypes: {
onCancel: PropTypes.func
},
getInitialState() {
return {
data: {
text: ''
}
};
},
request(data) {
lore.actions.tweet.create(data);
},
onSubmit() {
const { data } = this.state;
this.request(data);
this.dismiss();
},
dismiss() {
this.props.onCancel();
},
onChange(name, value) {
const nextData = _.merge({}, this.state.data);
nextData[name] = value;
this.setState({
data: nextData
});
},
render() {
const { data } = this.state;
return (
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<button type="button" className="close" onClick={this.dismiss}>
<span>×</span>
</button>
<h4 className="modal-title">
Create Tweet
</h4>
</div>
<div className="modal-body">
<div className="row">
<div className="col-md-12">
<div className="form-group">
<label>Message</label>
<textarea
className="form-control"
rows="3"
value={data.text}
placeholder="What's happening?"
onChange={(event) => {
this.onChange('text', event.target.value)
}}
/>
</div>
</div>
</div>
</div>
<div className="modal-footer">
<div className="row">
<div className="col-md-12">
<button
type="button"
className="btn btn-default"
onClick={this.dismiss}
>
Cancel
</button>
<button
type="button"
className="btn btn-primary"
disabled={!data.text}
onClick={this.onSubmit}
>
Create
</button>
</div>
</div>
</div>
</div>
</div>
);
}
});
// src/components/CreateTweetDialog.js
import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
class CreateTweetDialog extends React.Component {
constructor(props) {
super(props);
// set initial state
this.state = {
data: {
text: ''
}
};
// bind custom methods
this.request = this.request.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.onChange = this.onChange.bind(this);
}
request(data) {
lore.actions.tweet.create(data);
}
onSubmit() {
const { data } = this.state;
this.request(data);
this.dismiss();
}
onChange(name, value) {
const nextData = _.merge({}, this.state.data);
nextData[name] = value;
this.setState({
data: nextData
});
}
render() {
const { data } = this.state;
return (
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<button type="button" className="close" onClick={this.dismiss}>
<span>×</span>
</button>
<h4 className="modal-title">
Create Tweet
</h4>
</div>
<div className="modal-body">
<div className="row">
<div className="col-md-12">
<div className="form-group">
<label>Message</label>
<textarea
className="form-control"
rows="3"
value={data.text}
placeholder="What's happening?"
onChange={(event) => {
this.onChange('text', event.target.value)
}}
/>
</div>
</div>
</div>
</div>
<div className="modal-footer">
<div className="row">
<div className="col-md-12">
<button
type="button"
className="btn btn-default"
onClick={this.dismiss}
>
Cancel
</button>
<button
type="button"
className="btn btn-primary"
disabled={!data.text}
onClick={this.onSubmit}
>
Create
</button>
</div>
</div>
</div>
</div>
</div>
);
}
}
CreateTweetDialog.propTypes = {
title: PropTypes.node,
description: PropTypes.node
};
export default CreateTweetDialog;
// src/components/CreateTweetDialog.js
import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
class CreateTweetDialog extends React.Component {
static propTypes = {
title: PropTypes.node,
description: PropTypes.node
};
constructor(props) {
super(props);
// set initial state
this.state = {
data: {
text: ''
}
};
// bind custom methods
this.request = this.request.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.onChange = this.onChange.bind(this);
}
request(data) {
lore.actions.tweet.create(data);
}
onSubmit() {
const { data } = this.state;
this.request(data);
this.dismiss();
}
onChange(name, value) {
const nextData = _.merge({}, this.state.data);
nextData[name] = value;
this.setState({
data: nextData
});
}
render() {
const { data } = this.state;
return (
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<button type="button" className="close" onClick={this.dismiss}>
<span>×</span>
</button>
<h4 className="modal-title">
Create Tweet
</h4>
</div>
<div className="modal-body">
<div className="row">
<div className="col-md-12">
<div className="form-group">
<label>Message</label>
<textarea
className="form-control"
rows="3"
value={data.text}
placeholder="What's happening?"
onChange={(event) => {
this.onChange('text', event.target.value)
}}
/>
</div>
</div>
</div>
</div>
<div className="modal-footer">
<div className="row">
<div className="col-md-12">
<button
type="button"
className="btn btn-default"
onClick={this.dismiss}
>
Cancel
</button>
<button
type="button"
className="btn btn-primary"
disabled={!data.text}
onClick={this.onSubmit}
>
Create
</button>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default CreateTweetDialog;
Next we're going finish adding the create dialog.