Reputation: 123
I creating a comment form which would allow to post a comment below a video. It throws me id error inside the constructor that goes away on page reload. The ownProps comes out as empty object, otherwise I would've used it to get the video id. My solution with window.location.reload() is lame. Does anybody know a better one?
Comment Container
import React from 'react';
import { connect } from 'react-redux';
import Comment from './comment';
import { fetchComments, updateComment, createComment } from '../../util/comment_api_util';
const mSTP = state => {
const id = Object.keys(state.entities.videos);
const video = state.entities.videos[id];
if (video) {
return {
video,
}
} else {
window.location.reload()
}
}
const mDTP = dispatch => {
return {
fetchComments: () => dispatch(fetchComments()),
fetchComment: commentId => dispatch(fetchComment(commentId)),
createComment: comment => dispatch(createComment(comment)),
updateComment: comment => dispatch(updateComment(comment)),
deleteComment: commentId => dispatch(deleteComment(commentId))
}
}
export default connect(mSTP, mDTP)(Comment)
Comment Component
import React from 'react';
class Comment extends React.Component {
constructor(props) {
super(props)
this.state = {
body: "",
// video_id: this.props.video.id,
comment_errors: null,
}
this.update = this.update.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
update() {
return e => this.setState({ body: e.target.value })
}
handleSubmit(e) {
e.preventDefault();
const formData = new FormData();
formData.append('comment[body]', this.state.body);
formData.append('comment[video_id]', this.state.video_id);
$.ajax({
url: '/api/comments',
method: 'POST',
data: formData,
contentType: false,
processData: false
}).then(
(response) => {
this.setState(
{ comment_errors: response.responseJSON },
)
}
).then(() => {
this.setState(
{ body: "", video_id: "", comment_errors: null }
)
});
}
render() {
// if (!this.props.video) return <div />
return (
<div>
<form onSubmit={this.handleSubmit}>
<label>
<textarea
type="body"
placeholder="Add a comment"
value={this.state.body}
onChange={this.update()}
className="comment-body"/>
</label>
<button type="submit">Add comment</button>
</form>
</div>
)
}
}
export default Comment;
Play Container
import { connect } from 'react-redux';
import Play from './play';
import { fetchVideo } from '../../actions/video_actions';
import { fetchUsers } from '../../actions/user_actons';
import { fetchComments } from '../../actions/comment_actions';
const mSTP = (state, ownProps) => {
const users = Object.values(state.entities.users)
return {
video: state.entities.videos[ownProps.match.params.id],
users
}
};
const mDTP = dispatch => ({
fetchComments: () => dispatch(fetchComments()),
fetchVideo: videoId => dispatch(fetchVideo(videoId)),
fetchUsers: () => dispatch(fetchUsers()),
});
export default connect(mSTP, mDTP)(Play);
Play Component
import React from 'react';
import Comment from '../comment/comment_container'
class Play extends React.Component {
constructor(props) {
super(props);
}
dateCreated(date) {
const dateCreated = new Date(date)
return dateCreated.toLocaleDateString();
}
componentDidMount() {
this.props.fetchUsers();
this.props.fetchComments();
this.props.fetchVideo(this.props.match.params.id).then(() => {
const video = document.querySelector('.video-player');
video.muted = !video.muted;
video.play()
});
}
render() {
if (!this.props.video) { return null }
console.log(this.props)
const users = this.props.users;
const owner = users.filter(user => user.id === this.props.video.owner_id)[0]
return (
<div id="video-container">
<video
className="video-player"
controls="controls"
src={this.props.video.video_url}
autoPlay="autoplay"
muted
>
</video>
<div id="play-info">
<h1 className="play-title">{this.props.video.video_title}</h1>
<h2 className="play-date">{this.dateCreated(this.props.video.created_at)}</h2>
<h2 className="owner-name">{owner.username}</h2>
<h2 className="play-description">{this.props.video.video_description}</h2>
</div>
<Comment />
<div className="home-footer">
<h2 className="home-footer-1">@2020</h2>
<h2 className="home-footer-2">
Made with
<svg viewBox="0 0 20 20" className="_3Weix"><path d="M10 18a1.23 1.23 0 01-.8-.4 14.25 14.25 0 00-4.4-3.7C2.5 12.3 0 10.7 0 7.5a5.52 5.52 0 011.6-3.9A5.73 5.73 0 016 2a5.25 5.25 0 014 1.9A5.85 5.85 0 0114 2c2.9 0 6 2.2 6 5.5s-2.5 4.8-4.8 6.4a15.51 15.51 0 00-4.4 3.7 1.23 1.23 0 01-.8.4z" fill="rgb(255,0,0)"></path></svg>
NYC
</h2>
</div>
</div>
);
}
}
export default Play;
Upvotes: 1
Views: 664
Reputation: 1651
The only issue here is that the Comment
component is trying to render before video loads. I am assuming that you are fetching video which means that if it is not there even for an instant, this error will be thrown.
The solution to this is to conditionally render Comment
when you know that video exists. You can do this either in the parent or the child.
Remove this from mapStateToProps:
if (video) {
return {
video,
}
} else {
window.location.reload()
}
And check that video exists before rendering Comment
Possible solution in parent:
{video && video.id ? <Comment video={video} /> : null}
Additionally
You could render some sort of circular progress indicator instead of null
Possible solution in child
Remove video_id from state since this shouldnt be updated anyways and doesn't need to be state. Then check existence before return like this:
if (!video) return <div />
return (
// Your Comment jsx
)
Again you can render some sort of progress indicator instead of div if you want.
Why it works
Whenever video doesn't exist for that instant it will return div and wont crash. Once video exists it will re-render and now will load the Comment component instead of the div. Typically it will happen so quickly that you wont notice.
Upvotes: 1