Quickstart
A quick dive into getting started with Lore
A quick dive into getting started with Lore
In this step we'll look at an alternative approach to hiding components that doesn't use decorators.
You can view the finished code for this step by checking out the
authorization.3
branch of the completed project.
While decorators can provide a concise way to add behavior to an application, they're not very easy to understand compared to a simple React component. Conceptually, they're functions that return a function that return a component that renders YOUR component, which can be difficult to visualize.
In this section, we'll introduce an alternative way of hiding components based on authorization rules, and we'll use a simple component instead of a decorator.
Start by removing the UserCanEditTweet
decorator from the EditLink
, and removing the UserCanDeleteTweet
decorator from the DeleteLink
.
Once you do this, the "edit" and "delete" links should be visible for all tweets.
Next, create a new component called IsOwner
:
lore generate component IsOwner
Then replace the code with this:
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
export default createReactClass({
displayName: 'IsOwner',
propTypes: {
tweet: PropTypes.object.isRequired
},
contextTypes: {
user: PropTypes.object.isRequired
},
render() {
const { tweet, children } = this.props;
const { user } = this.context;
if (tweet.data.user === user.id) {
return children;
}
return null;
}
});
import React from 'react';
import PropTypes from 'prop-types';
class IsOwner extends React.Component {
render() {
const { tweet, children } = this.props;
const { user } = this.context;
if (tweet.data.user === user.id) {
return children;
}
return null;
}
}
IsOwner.propTypes = {
tweet: PropTypes.object.isRequired
};
IsOwner.contextTypes = {
user: PropTypes.object.isRequired
};
export default IsOwner;
import React from 'react';
import PropTypes from 'prop-types';
class IsOwner extends React.Component {
static propTypes = {
tweet: PropTypes.object.isRequired
};
static contextTypes = {
user: PropTypes.object.isRequired
};
render() {
const { tweet, children } = this.props;
const { user } = this.context;
if (tweet.data.user === user.id) {
return children;
}
return null;
}
}
export default IsOwner;
Similar to the decorators we created previously, this component expects to receive a tweet
as a prop, along with the user
from context.
In the render()
method, we compare the current user to the user who created the tweet. If they match, we render whatever children (other components) were provided. If they don't, we render nothing.
To see how we use this it, open the Tweet
component. Import IsOwner
, and wrap our edit
and delete
links with it like this:
// src/components/Tweet.js
...
import IsOwner from './IsOwner';
...
render() {
...
<IsOwner tweet={tweet}>
<div className="tweet-actions">
<EditLink tweet={tweet} />
<DeleteLink tweet={tweet} />
</div>
</IsOwner>
...
}
...
With this change in place, refresh the page, and once again, the "edit" and "delete" links should only be visible on tweets created by the current user.
If everything went well, your application should now look like this.
If you chose to follow, below is a list of files modified during this step.
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
export default createReactClass({
displayName: 'IsOwner',
propTypes: {
tweet: PropTypes.object.isRequired
},
contextTypes: {
user: PropTypes.object.isRequired
},
render() {
const { tweet, children } = this.props;
const { user } = this.context;
if (tweet.data.user === user.id) {
return children;
}
return null;
}
});
import React from 'react';
import PropTypes from 'prop-types';
class IsOwner extends React.Component {
render() {
const { tweet, children } = this.props;
const { user } = this.context;
if (tweet.data.user === user.id) {
return children;
}
return null;
}
}
IsOwner.propTypes = {
tweet: PropTypes.object.isRequired
};
IsOwner.contextTypes = {
user: PropTypes.object.isRequired
};
export default IsOwner;
import React from 'react';
import PropTypes from 'prop-types';
class IsOwner extends React.Component {
static propTypes = {
tweet: PropTypes.object.isRequired
};
static contextTypes = {
user: PropTypes.object.isRequired
};
render() {
const { tweet, children } = this.props;
const { user } = this.context;
if (tweet.data.user === user.id) {
return children;
}
return null;
}
}
export default IsOwner;
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
export default createReactClass({
displayName: 'EditLink',
propTypes: {
tweet: PropTypes.object.isRequired
},
onClick() {
const { tweet } = this.props;
lore.dialog.show(function() {
return lore.dialogs.tweet.update(tweet, {
blueprint: 'optimistic',
request: function(data) {
return lore.actions.tweet.update(tweet, data).payload;
}
});
});
},
render() {
return (
<a className="link" onClick={this.onClick}>
edit
</a>
);
}
});
import React from 'react';
import PropTypes from 'prop-types';
class EditLink extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick() {
const { tweet } = this.props;
lore.dialog.show(function() {
return lore.dialogs.tweet.update(tweet, {
blueprint: 'optimistic',
request: function(data) {
return lore.actions.tweet.update(tweet, data).payload;
}
});
});
}
render() {
return (
<a className="link" onClick={this.onClick}>
edit
</a>
);
}
}
EditLink.propTypes = {
tweet: PropTypes.object.isRequired
};
export default EditLink;
import React from 'react';
import PropTypes from 'prop-types';
class EditLink extends React.Component {
static propTypes = {
tweet: PropTypes.object.isRequired
};
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick() {
const { tweet } = this.props;
lore.dialog.show(function() {
return lore.dialogs.tweet.update(tweet, {
blueprint: 'optimistic',
request: function(data) {
return lore.actions.tweet.update(tweet, data).payload;
}
});
});
}
render() {
return (
<a className="link" onClick={this.onClick}>
edit
</a>
);
}
}
export default EditLink;
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
export default createReactClass({
displayName: 'DeleteLink',
propTypes: {
tweet: PropTypes.object.isRequired
},
onClick() {
const { tweet } = this.props;
lore.dialog.show(function() {
return lore.dialogs.tweet.destroy(tweet, {
blueprint: 'optimistic',
request: function(data) {
return lore.actions.tweet.destroy(tweet).payload;
}
});
});
},
render() {
return (
<a className="link" onClick={this.onClick}>
delete
</a>
);
}
});
import React from 'react';
import PropTypes from 'prop-types';
class DeleteLink extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick() {
const { tweet } = this.props;
lore.dialog.show(function() {
return lore.dialogs.tweet.destroy(tweet, {
blueprint: 'optimistic',
request: function(data) {
return lore.actions.tweet.destroy(tweet).payload;
}
});
});
}
render() {
return (
<a className="link" onClick={this.onClick}>
delete
</a>
);
}
}
DeleteLink.propTypes = {
tweet: PropTypes.object.isRequired
};
export default DeleteLink;
import React from 'react';
import PropTypes from 'prop-types';
class DeleteLink extends React.Component {
static propTypes = {
tweet: PropTypes.object.isRequired
};
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick() {
const { tweet } = this.props;
lore.dialog.show(function() {
return lore.dialogs.tweet.destroy(tweet, {
blueprint: 'optimistic',
request: function(data) {
return lore.actions.tweet.destroy(tweet).payload;
}
});
});
}
render() {
return (
<a className="link" onClick={this.onClick}>
delete
</a>
);
}
}
export default DeleteLink;
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import moment from 'moment';
import { connect } from 'lore-hook-connect';
import EditLink from './EditLink';
import DeleteLink from './DeleteLink';
import IsOwner from './IsOwner';
export default connect(function(getState, props) {
const { tweet } = props;
return {
user: getState('user.byId', {
id: tweet.data.user
})
};
})(
createReactClass({
displayName: 'Tweet',
propTypes: {
tweet: PropTypes.object.isRequired,
user: PropTypes.object.isRequired
},
render() {
const { tweet, user } = this.props;
const timestamp = moment(tweet.data.createdAt).fromNow().split(' ago')[0];
return (
<li className="list-group-item tweet">
<div className="image-container">
<img
className="img-circle avatar"
src={user.data.avatar} />
</div>
<div className="content-container">
<h4 className="list-group-item-heading title">
{user.data.nickname}
</h4>
<h4 className="list-group-item-heading timestamp">
{'- ' + timestamp}
</h4>
<p className="list-group-item-text text">
{tweet.data.text}
</p>
<IsOwner tweet={tweet}>
<div className="tweet-actions">
<EditLink tweet={tweet} />
<DeleteLink tweet={tweet} />
</div>
</IsOwner>
</div>
</li>
);
}
})
);
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import { connect } from 'lore-hook-connect';
import EditLink from './EditLink';
import DeleteLink from './DeleteLink';
import IsOwner from './IsOwner';
class Tweet extends React.Component {
render() {
const { tweet, user } = this.props;
const timestamp = moment(tweet.data.createdAt).fromNow().split(' ago')[0];
return (
<li className="list-group-item tweet">
<div className="image-container">
<img
className="img-circle avatar"
src={user.data.avatar} />
</div>
<div className="content-container">
<h4 className="list-group-item-heading title">
{user.data.nickname}
</h4>
<h4 className="list-group-item-heading timestamp">
{'- ' + timestamp}
</h4>
<p className="list-group-item-text text">
{tweet.data.text}
</p>
<IsOwner tweet={tweet}>
<div className="tweet-actions">
<EditLink tweet={tweet} />
<DeleteLink tweet={tweet} />
</div>
</IsOwner>
</div>
</li>
);
}
}
Tweet.propTypes = {
tweet: PropTypes.object.isRequired,
user: PropTypes.object.isRequired
};
export default connect(function(getState, props) {
const tweet = props.tweet;
return {
user: getState('user.byId', {
id: tweet.data.user
})
};
})(Tweet);
import React from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import { connect } from 'lore-hook-connect';
import EditLink from './EditLink';
import DeleteLink from './DeleteLink';
import IsOwner from './IsOwner';
@connect(function(getState, props) {
const tweet = props.tweet;
return {
user: getState('user.byId', {
id: tweet.data.user
})
};
})
class Tweet extends React.Component {
static propTypes = {
tweet: PropTypes.object.isRequired,
user: PropTypes.object.isRequired
};
render() {
const { tweet, user } = this.props;
const timestamp = moment(tweet.data.createdAt).fromNow().split(' ago')[0];
return (
<li className="list-group-item tweet">
<div className="image-container">
<img
className="img-circle avatar"
src={user.data.avatar} />
</div>
<div className="content-container">
<h4 className="list-group-item-heading title">
{user.data.nickname}
</h4>
<h4 className="list-group-item-heading timestamp">
{'- ' + timestamp}
</h4>
<p className="list-group-item-text text">
{tweet.data.text}
</p>
<IsOwner tweet={tweet}>
<div className="tweet-actions">
<EditLink tweet={tweet} />
<DeleteLink tweet={tweet} />
</div>
</IsOwner>
</div>
</li>
);
}
}
export default Tweet;
In the next section we'll learn how to display new tweets at the top of the Feed.