Prashant Agarwal
Prashant Agarwal

Reputation: 37

How to modify a specific component of a list of component rendered using map in react?

I have a PostList component with an array of posts objects. I am rendering this list of post using another pure functional component Post using Array.map() method. Post component has another component - LikeButton to like or unlike a post. Now I want to show a spinner during like or unlike on top of that LikeButton component. LikeButton Component looks something like this:

const LikeButton = (props) => {
  const likeBtnClasses = [classes.LikeBtn];
  const loggedInUserId = useSelector((state) => state.auth.user.id);
  const isLoading = useSelector((state) => state.post.loading);
  const isPostLiked = props.post.likes.find(
    (like) => like.user === loggedInUserId
  );

  const [isLiked, setLike] = useState(isPostLiked ? true : false);

  const token = useSelector((state) => state.auth.token);
  const dispatch = useDispatch();

  if (isLiked) {
    likeBtnClasses.push(classes.Highlight);
  }

  const postLikeHandler = () => {
    if (!isLiked) {
      setLike(true);
      dispatch(actions.likePost(props.post._id, token));
    } else {
      setLike(false);
      dispatch(actions.unlikePost(props.post._id, token));
    }
  };

  return isLoading ? (
    <Spinner />
  ) : (
    <button
      className={likeBtnClasses.join(" ")}
      onClick={() => postLikeHandler()}
    >
      <i class="far fa-thumbs-up"></i>
      <small>{props.post.likes.length}</small>
    </button>
  );
};

Instead of showing the spinner to that single post, I am seeing it on all the posts. My Post component looks like this:

const Post = (props) => {
  return (
    <div className={classes.Post}>
      <div className={classes.Author}>
        <img src={props.postData.avatar} alt="avatar" />
        <div className={classes.AuthorDetails}>
          <h3>{props.postData.name}</h3>
        </div>
      </div>
      <div className={classes.PostText}>
        <p>{props.postData.text}</p>
      </div>
      <hr />
      <div className={classes.PostTools}>
        <LikeButton post={props.postData} />
        <div className={classes.PostBtn}>
          <i class="far fa-comments"></i>
          <small>3</small>
        </div>
        <div className={classes.PostBtn}>
          <i class="fas fa-share"></i>
          <small>2</small>
        </div>
      </div>
    </div>
  );
};

PostList component:

class PostList extends React.Component {
  state = {
    posts: [
      {
        text: "POST1",
        user: "XYZ",
        name: "XYZ",
        id: "post1",
        likes: [],
      },
      {
        text: "POST2",
        user: "[email protected]",
        name: "John Doe",
        id: "post2",
        likes: [],
      },
    ],
  };

  componentDidMount() {
    if (this.props.token) {
      this.props.onFetchPosts(this.props.token);
      this.props.onFetchUserAuthData(this.props.token);
    }
  }
  render() {
    let posts = null;
    if (this.props.posts.length === 0) {
      posts = this.state.posts.map((post) => {
        return <Post key={post.id} postData={post} />;
      });
    } else {
      posts = this.props.posts.map((post) => {
        return <Post key={post._id} postData={post} />;
      });
    }
    return (
      <div>
        <CreatePost />
        {posts}
      </div>
    );
  }
}

const mapStateToProps = (state) => {
  return {
    token: state.auth.token,
    posts: state.post.posts,
    loading: state.post.loading,
    error: state.post.err,
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    onFetchPosts: (token) => dispatch(actions.fetchPosts(token)),
    onFetchUserAuthData: (token) => dispatch(actions.fetchUser(token)),
  };
};

Upvotes: 1

Views: 56

Answers (1)

Santosh Shinde
Santosh Shinde

Reputation: 6063

Please do some change in your to checking like/unlike is loading or not for the LikeButton.

       const LikeButton = (props) => {
                ....
                const [isButtonLoading, setButtonLoading] = useState(false);
                ...

                return isButtonLoading ? (
                    <Spinner />
                ) : (
                    <button
                    className={likeBtnClasses.join(" ")}
                    onClick={() => postLikeHandler();setButtonLoading(true)}
                    >
                    <i class="far fa-thumbs-up"></i>
                    <small>{props.post.likes.length}</small>
                    </button>
                );
        };

Then on your dispatch callback need to set the isButtonLoading value to false.

        const buttonCallback() {
            // here we need to reset our flag
            setButtonLoading(false);
        }

        const postLikeHandler = () => {
            if (!isLiked) {
               setLike(true);
               // for this action you need to create third parameter called as callback so after response our buttonCallback will call
               dispatch(actions.likePost(props.post._id, token, buttonCallback));
            } else {
               setLike(false);
               // for this action you need to create third parameter called as callback so after response our buttonCallback will call
               dispatch(actions.unlikePost(props.post._id, token, buttonCallback);
            }
        };

fore more details please check here.

Hope this will help you.

Upvotes: 1

Related Questions