user13790968
user13790968

Reputation: 123

Uncaught TypeError: Cannot read property 'id' of undefined / window.location.reload()

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;

enter image description here

Upvotes: 1

Views: 664

Answers (1)

Grant Singleton
Grant Singleton

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

Related Questions