Quickstart
A quick dive into getting started with Lore
A quick dive into getting started with Lore
In this step fix the error and restore functionality to the application.
You can view the finished code for this step by checking out the
optimistic.4
branch of the completed project.
Currently when you create a tweet, this error appears in the console:
Invalid call to "getState('user.byId')". Missing required attribute "id".
This error is happening because we're trying to render a tweet that doesn't have a user
property yet. The code throwing the error is the connect
decorator in the Tweet
, shown below:
export default connect(function(getState, props) {
const tweet = props.tweet;
return {
user: getState('user.byId', {
id: tweet.data.user
})
};
})
export default connect(function(getState, props) {
const tweet = props.tweet;
return {
user: getState('user.byId', {
id: tweet.data.user
})
};
})(Tweet);
@connect(function(getState, props) {
const tweet = props.tweet;
return {
user: getState('user.byId', {
id: tweet.data.user
})
};
})
When we create data, the API will set the user
property to the user who created it, and the createdAt
date to the timestamp of when the tweet was saved to the database.
But currently, we're rendering this data before those fields are assigned, which means tweet.data.user
is undefined
.
There are three ways we can solve this:
Tweet
component so that it behaves differently when no user
field exists, like showing a generic avatar photo.OptimisticTweet
, specifically designed for rendering partial data, and show that instead of Tweet
when appropriate.We're going to go with the third option, and add the missing fields ourselves.
Open the CreateButton
component, import the user from context, and update the onClick()
callback to look like this:
// src/components/CreateButton.js
import _ from 'lodash';
...
export default createReactClass({
displayName: 'CreateButton',
contextTypes: {
user: PropTypes.object.isRequired
},
onClick() {
const { user } = this.context;
lore.dialog.show(function() {
return lore.dialogs.tweet.create({
blueprint: 'optimistic',
request: function(data) {
return lore.actions.tweet.create(_.defaults({
user: user.id,
createdAt: new Date().toISOString()
}, data)).payload;
}
});
});
},
...
});
// src/components/CreateButton.js
import _ from 'lodash';
...
class CreateButton extends React.Component {
...
onClick() {
const { user } = this.context;
lore.dialog.show(function() {
return lore.dialogs.tweet.create({
blueprint: 'optimistic',
request: function(data) {
return lore.actions.tweet.create(_.defaults({
user: user.id,
createdAt: new Date().toISOString()
}, data)).payload;
}
});
});
}
...
}
CreateButton.contextTypes = {
user: PropTypes.object.isRequired
};
export default CreateButton;
// src/components/CreateButton.js
import _ from 'lodash';
...
class CreateButton extends React.Component {
static contextTypes = {
user: PropTypes.object.isRequired
};
...
onClick() {
const { user } = this.context;
lore.dialog.show(function() {
return lore.dialogs.tweet.create({
blueprint: 'optimistic',
request: function(data) {
return lore.actions.tweet.create(_.defaults({
user: user.id,
createdAt: new Date().toISOString()
}, data)).payload;
}
});
});
}
...
}
export default CreateButton;
Since we know the tweet is being created by the current user, the first thing we do is get the user
from context. Then, instead of passing data
directly to the create
action, we're setting the user
and createdAt
properties to what we know they'll be after the API request returns.
To be clear, we're not actually assigning values to these fields, in the sense of telling the API what they should be.
While these fields will be included the body of the PUT request, the API will ignore them, and set
user
based on the API token, andcreatedAt
based on the timestamp of when the tweet was saved to the database.All we're doing is here providing values that will allow the tweet to be rendered correctly in the interim, between the time when the request is sent, and when it comes back from the server with official values.
If you now refresh the browser, and create a tweet, you'll notice the application not only works again, but that the Tweet immediately appears in the Feed.
If everything went well, your application should now look like this (exactly the same).
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';
import _ from 'lodash';
export default createReactClass({
displayName: 'CreateButton',
contextTypes: {
user: PropTypes.object.isRequired
},
onClick() {
const { user } = this.context;
lore.dialog.show(function() {
return lore.dialogs.tweet.create({
blueprint: 'optimistic',
request: function(data) {
return lore.actions.tweet.create(_.defaults({
user: user.id,
createdAt: new Date().toISOString()
}, data)).payload;
}
});
});
},
render() {
return (
<button
type="button"
className="btn btn-primary btn-lg create-button"
onClick={this.onClick}>
+
</button>
);
}
});
import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import CreateTweetDialog from './CreateTweetDialog';
class CreateButton extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick() {
const { user } = this.context;
lore.dialog.show(function() {
return lore.dialogs.tweet.create({
blueprint: 'optimistic',
request: function(data) {
return lore.actions.tweet.create(_.defaults({
user: user.id,
createdAt: new Date().toISOString()
}, data)).payload;
}
});
});
}
render () {
return (
<button
type="button"
className="btn btn-primary btn-lg create-button"
onClick={this.onClick}>
+
</button>
);
}
}
CreateButton.contextTypes = {
user: PropTypes.object.isRequired
};
export default CreateButton;
import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import CreateTweetDialog from './CreateTweetDialog';
class CreateButton extends React.Component {
static contextTypes = {
user: PropTypes.object.isRequired
};
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
}
onClick() {
const { user } = this.context;
lore.dialog.show(function() {
return lore.dialogs.tweet.create({
blueprint: 'optimistic',
request: function(data) {
return lore.actions.tweet.create(_.defaults({
user: user.id,
createdAt: new Date().toISOString()
}, data)).payload;
}
});
});
}
render () {
return (
<button
type="button"
className="btn btn-primary btn-lg create-button"
onClick={this.onClick}>
+
</button>
);
}
}
export default CreateButton;
In the next section we'll add a visual cue when tweets are being created, updated or deleted.