1

What I have is a feed where you can post entries. These entries can then be liked by any user. The problem is that after you like an entry and continue with writing your own entry right after(thus pushing the new entry to the top of the feed) the state of all the like buttons that you have clicked will be undone visually and in order to see the correct data being displayed a manual refresh is required.

This problem is very peculiar and I have unfortunately not found anything that could relate to this.

Here is my code for the Like component(like.es6.jsx):

class Like extends React.Component {
    constructor(props){
        super(props);
        this.state={
            like: this.props.entry.liked,
            likes: this.props.entry.likes_count
        }
        this.post = this.post.bind(this);
        this.delete = this.delete.bind(this);
    }

    post () {
        if(!(this.state.like)){
            $.ajax({
                    method: "POST",
                    url: "/api/v1/likes",
                    data: {
                        log_entry_id: this.props.entry.id
                    },
                })
                .then( (response) => {
                    this.setState({like: true})
                    let likes = this.state.likes;
                    likes++;
                    this.setState({likes: likes})
                });
        }
    }

    delete(){
        if(this.state.like){
            $.ajax({
                    method: "DELETE",
                    url: "/api/v1/likes/" + this.props.entry.id
                })
                .then( (response) => {
                    this.setState({like: false})
                    let likes = this.state.likes;
                    likes--;
                    this.setState({likes: likes})
                });
        }
    }

    render () {
        let show_likes = "";
        if(this.state.like) {
            show_likes = (this.state.likes > 1) ? ("You and " + (this.state.likes - 1) + ((this.state.likes == 2) ? (" other person") : (" others" )) + (" like this.")) : ("You like this.");
        }else if(this.state.likes > 0){
            show_likes = (this.state.likes == 1) ? (this.state.likes + " person likes this.") : (this.state.likes + " people like this.");
        }
        let like_button = this.state.like ?
            <a onClick={this.delete} className=" pull-right"><i className="fa fa-thumbs-o-down"></i> Dislike</a> :
            <a onClick={this.post} className=" pull-right"><i className="fa fa-thumbs-o-up"></i> Like</a>
        return (
            <div>
            <span>
                {show_likes}
            </span>
                {like_button}
            </div>
        );
    }
}

In the parent component 'log_entry.es6.jsx' I've added my like component in the following way:

<Like entry={entry} key={entry.id} />

If there's any more information I can supply with I'll happily do it upon request. Thank you for your time reviewing my problem!

lax1n
  • 59
  • 1
  • 8

2 Answers2

1

When you "continue with writing your own entry", does that update this.state in a parent component? If so, I bet it's causing Like to re-render with its props, which have become out-of-date!

Think of it this way: the entry passed into <Like entry={entry} /> only includes data which was true at initial page load. When a user clicks "like", it updates this.date of the Like component, but the original entry was not updated!

So, if something causes Like to re-render from entry, it will be rendered with stale data! But, if you refresh the page, it will fetch the latest data and render correctly.

Does that sound like a likely cause?

The best solution I can think of is the container component pattern. In this pattern, only one component (at the top of the tree) has this.state. It sends data down to its children as props. Only the container component sends AJAX requests and updates this.state.

In your case, that would mean moving the post and delete functions to the parent component, and passing them into Like as event handlers (for example, <Like onLike={this.post} onUnlike={this.delete} ...>) Then, in Like, you could use those as on-click handlers, eg, onClick={this.onLike}.

Although this requires careful organization, it really pays off because every render is very predictable: the only thing that can change is the container's this.state, everything else renders from this.props only!

rmosolgo
  • 1,854
  • 1
  • 18
  • 23
  • thank you for your response. It does sound like a likely cause yes and I will attempt to implement this solution in my code. I'll come back to you with what I get :) – lax1n Feb 15 '16 at 09:08
0

I tried moving the functions and state handlers to the parent component. However this proved very troublesome due to the fact that the parent of my Like button also had a parent(perhaps I could've exhausted this method further, but I found a different way).

My solution ended up being this:

class Like extends React.Component {
    constructor(props){
        super(props);
        this.state={
            like: this.props.entry.liked,
            likes: this.props.entry.likes_count
        }
        this.post = this.post.bind(this);
        this.delete = this.delete.bind(this);
    }

    post () {
        if(!(this.state.like)){
            $.ajax({
                    method: "POST",
                    url: "/api/v1/likes",
                    data: {
                        log_entry_id: this.props.entry.id
                    },
                })
                .then( (response) => {
                    this.setState({like: true})
                    let likes = this.state.likes;
                    likes++;
                    this.setState({likes: likes});
                    this.props.entry.liked = true;
                    this.props.entry.likes_count = likes;
                });
        }
    }

    delete(){
        if(this.state.like){
            $.ajax({
                    method: "DELETE",
                    url: "/api/v1/likes/" + this.props.entry.id
                })
                .then( (response) => {
                    this.setState({like: false})
                    let likes = this.state.likes;
                    likes--;
                    this.setState({likes: likes});
                    this.props.entry.liked = false;
                    this.props.entry.likes_count = likes;
                });
        }
    }

    render () {
        let show_likes = "";
        if(this.state.like) {
            show_likes = (this.state.likes > 1) ? ("You and " + (this.state.likes - 1) + ((this.state.likes == 2) ? (" other person") : (" others" )) + (" like this.")) : ("You like this.");
        }else if(this.state.likes > 0){
            show_likes = (this.state.likes == 1) ? (this.state.likes + " person likes this.") : (this.state.likes + " people like this.");
        }
        let like_button = this.state.like ?
            <a onClick={this.delete} className=" pull-right"><i className="fa fa-thumbs-o-down"></i> Dislike</a> :
            <a onClick={this.post} className=" pull-right"><i className="fa fa-thumbs-o-up"></i> Like</a>
        return (
            <div>
            <span>
                {show_likes}
            </span>
                {like_button}
            </div>
        );
    }
}

What I added was 2 lines on both the post and delete function.

this.props.entry.liked = true;
this.props.entry.likes_count = likes;

this.props.entry.liked = false;
this.props.entry.likes_count = likes;

Simply updating the props for this entry. I'm not sure if this is the best solution, but for me it helps me keep the functionality where it belongs and of course it also works.

lax1n
  • 59
  • 1
  • 8