Реагировать компонент не получает реквизиты при обновлении магазина Redux - PullRequest
0 голосов
/ 01 апреля 2020

Я создаю приложение для социальных сетей, используя стек MERN и Redux в качестве менеджера состояний. У меня есть компонент канала, который отображает компоненты PostItem, которые отображают сообщение и позволяют выполнять такие действия, как симпатия и комментирование. У меня также есть компонент Post, который отображает тот же компонент PostItem, который открывается, когда пользователь нажимает кнопку комментария на компоненте PostItem в ленте. Когда мне нравится публикация через компонент фида, она получает обновленный реквизит и повторно отображает компонент, показывающий изменения. Однако, когда я нажимаю кнопку «Мне нравится» в компоненте Post, он обновляет хранилище Redux, но не получает обновленные реквизиты.

Feed. js

import React, { Fragment, useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { getPosts } from '../../actions/post';

// Components
import UserProfileCard from './UserProfileCard';
import PostForm from './PostForm';
import Footer from '../layout/Footer';
import PostItem from '../posts/PostItem';
import Spinner from '../layout/Spinner';

const Feed = ({ getPosts, post: { posts, loading } }) => {
  //Same as component did mount
  useEffect(() => {
    getPosts();
  }, [getPosts]);
  return (
    <Fragment>
      <div className='main-container mt-3'>
        <div className='container'>
          <div className='row'>
            <UserProfileCard />
            <PostForm />
          </div>
          {loading ? (
            <Spinner />
          ) : (
            posts.map(post => <PostItem key={post._id} post={post} />)
          )}
        </div>
      </div>
      <Footer />
    </Fragment>
  );
};

Feed.propTypes = {
  getPosts: PropTypes.func.isRequired,
  post: PropTypes.object.isRequired
};

const mapStateToProps = state => ({
  post: state.post
});

export default connect(mapStateToProps, { getPosts })(Feed);

Post. js

import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { getPost } from '../../actions/post';
import { Link } from 'react-router-dom';

// Components
import Spinner from '../layout/Spinner';
import PostItem from './PostItem';
import PostCommentForm from './PostCommentForm';
import Comment from './Comment';

// Assets
import { ArrowLeft } from 'react-feather';

const Post = ({ getPost, post: { post, loading }, match }) => {
  useEffect(() => {
    // get id from url in params for getPost function
    getPost(match.params.id);
  }, [getPost]);
  return loading || post === null ? (
    <Spinner />
  ) : (
    <div className='main-container mt-3'>
      <div className='container'>
        <div className='row'>
          {/* TODO ADD BROWSER HISTORY FUNCTIONALITY TO ALLOW USER TO GO BACK TO PROFILE OR FEED */}
          <Link className='mb-1' to='/feed'>
            <button className='btn btn-logo-color'>
              <ArrowLeft />
            </button>
          </Link>
          <PostItem key={post._id} post={post} />
          <PostCommentForm />
        </div>
        <Comment />
      </div>
    </div>
  );
};

Post.propTypes = {
  getPost: PropTypes.func.isRequired,
  post: PropTypes.object.isRequired
};

const mapStateToProps = state => ({
  post: state.post
});

export default connect(mapStateToProps, { getPost })(Post);

PostItem. js

import React from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import Moment from 'react-moment';
import { connect } from 'react-redux';
import { addLike, removeLike, deletePost } from '../../actions/post';

// Assets
import { ThumbsUp, ThumbsDown, MessageSquare, XCircle } from 'react-feather';
import avi from '../assets/default-avatar.png';

const PostItem = ({
  auth,
  post: { _id, text, firstname, lastname, user, likes, comments, date },
  addLike,
  removeLike,
  deletePost
}) => {
  return (
    <div className='card mt-1 post'>
      <div className='card-body'>
        <div className='row'>
          <div className='col-lg-2'>
            <Link className='feed-link' to={`/profile/${user}`}>
              <img src={avi} alt='avatar' className='avatar' />
              <h5 className='card-title mt-2'>
                {firstname} {lastname}
              </h5>
            </Link>
          </div>
          <div className='col-lg-10'>
            <p className='card-text'>{text}</p>
            <p className='text-muted post-date'>
              <Moment format='LLL'>{date}</Moment>
            </p>

            <div className='post-buttons'>
              <button
                type='button'
                className='btn btn-outline-primary mr-1'
                onClick={e => addLike(_id)}
              >
                <ThumbsUp />
                <span className='badge badge-light'>
                  {likes.length > 0 && <span>{likes.length}</span>}
                </span>
              </button>
              <button
                type='button'
                className='btn btn-outline-danger mr-1'
                onClick={e => removeLike(_id)}
              >
                <ThumbsDown />
              </button>
              {/* TODO ADD CONDITIONAL RENDERING TO REMOVE WHEN POST IS OPEN */}

              <Link to={`/post/${_id}`}>
                <button type='button' className='btn btn-outline-info mr-1'>
                  <MessageSquare />
                  <span className='badge badge-light'>
                    {comments.length > 0 && (
                      <span className='comment-count'>{comments.length}</span>
                    )}
                  </span>
                </button>
              </Link>
              {!auth.loading && user === auth.user._id && (
                <button
                  type='button'
                  className='btn btn-outline-danger mr-1'
                  onClick={() => deletePost(_id)}
                >
                  <XCircle />
                </button>
              )}
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

PostItem.propTypes = {
  post: PropTypes.object.isRequired,
  auth: PropTypes.object.isRequired,
  addLike: PropTypes.func.isRequired,
  removeLike: PropTypes.func.isRequired,
  deletePost: PropTypes.func.isRequired
};

const mapStateToProps = state => ({
  auth: state.auth
});

export default connect(mapStateToProps, { addLike, removeLike, deletePost })(
  PostItem
);

Post Reducer

import {
  GET_POSTS,
  POST_ERROR,
  UPDATE_LIKES,
  DELETE_POST,
  ADD_POST,
  GET_POST,
  ADD_COMMENT,
  REMOVE_COMMENT
} from '../actions/types';

const initialState = {
  posts: [],
  post: null,
  loading: true,
  error: {}
};

export default function(state = initialState, action) {
  const { type, payload } = action;

  switch (type) {
    case GET_POSTS:
      return {
        ...state,
        posts: payload,
        loading: false
      };
    case GET_POST:
      return {
        ...state,
        post: payload,
        loading: false
      };
    case ADD_POST:
      return {
        ...state,
        posts: [payload, ...state.posts],
        loading: false
      };
    case DELETE_POST:
      return {
        ...state,
        posts: state.posts.filter(post => post._id !== payload),
        loading: false
      };
    case POST_ERROR:
      return {
        ...state,
        error: payload,
        loading: false
      };
    case UPDATE_LIKES:
      return {
        ...state,
        posts: state.posts.map(post =>
          post._id === payload.postId ? { ...post, likes: payload.likes } : post
        ),
        loading: false
      };
    case ADD_COMMENT:
      return {
        ...state,
        post: { ...state.post, comments: payload },
        loading: false
      };
    case REMOVE_COMMENT:
      return {
        ...state,
        post: {
          ...state.post,
          comments: state.post.comments.filter(
            comment => comment._id !== payload
          )
        },
        loading: false
      };
    default:
      return {
        ...state
      };
  }
}

Действия после публикации

import axios from 'axios';
import { setAlert } from './alert';
import {
  GET_POSTS,
  POST_ERROR,
  UPDATE_LIKES,
  DELETE_POST,
  ADD_POST,
  GET_POST,
  ADD_COMMENT,
  REMOVE_COMMENT
} from './types';

//Get posts

export const getPosts = () => async dispatch => {
  try {
    const res = await axios.get('/api/posts');

    dispatch({
      type: GET_POSTS,
      payload: res.data
    });
  } catch (error) {
    dispatch({
      type: POST_ERROR,
      payload: { msg: error.response.data.msg, status: error.response.status }
    });
  }
};

// Add like
export const addLike = postId => async dispatch => {
  try {
    const res = await axios.put(`/api/posts/like/${postId}`);

    dispatch({
      type: UPDATE_LIKES,
      payload: { postId, likes: res.data }
    });
  } catch (error) {
    dispatch({
      type: POST_ERROR,
      payload: { msg: error.response.data.msg, status: error.response.status }
    });
  }
};

// Remove like
export const removeLike = postId => async dispatch => {
  try {
    const res = await axios.put(`/api/posts/unlike/${postId}`);

    dispatch({
      type: UPDATE_LIKES,
      payload: { postId, likes: res.data }
    });
  } catch (error) {
    dispatch({
      type: POST_ERROR,
      payload: { msg: error.response.data.msg, status: error.response.status }
    });
  }
};

// Add Post
export const addPost = formData => async dispatch => {
  const config = {
    headers: {
      'Content-Type': 'application/json'
    }
  };
  try {
    const res = await axios.post('/api/posts', formData, config);

    dispatch({
      type: ADD_POST,
      payload: res.data
    });

    dispatch(setAlert('Post Created', 'success'));
  } catch (error) {
    dispatch({
      type: POST_ERROR,
      payload: { msg: error.response.data.msg, status: error.response.status }
    });
  }
};

// Delete Post
export const deletePost = id => async dispatch => {
  try {
    const res = await axios.delete(`/api/posts/${id}`);

    dispatch({
      type: DELETE_POST,
      payload: id
    });

    dispatch(setAlert('Post Removed', 'success'));
  } catch (error) {
    dispatch({
      type: POST_ERROR,
      payload: { msg: error.response.data.msg, status: error.response.status }
    });
  }
};

//Get post
export const getPost = id => async dispatch => {
  try {
    const res = await axios.get(`/api/posts/${id}`);

    dispatch({
      type: GET_POST,
      payload: res.data
    });
  } catch (error) {
    dispatch({
      type: POST_ERROR,
      payload: { msg: error.response.data.msg, status: error.response.status }
    });
  }
};

// Add Comment
export const addComment = (postId, formData) => async dispatch => {
  const config = {
    headers: {
      'Content-Type': 'application/json'
    }
  };
  try {
    const res = await axios.post(
      `/api/posts/comment/${postId}`,
      formData,
      config
    );

    dispatch({
      type: ADD_COMMENT,
      payload: res.data
    });

    dispatch(setAlert('Comment Added', 'success'));
  } catch (error) {
    dispatch({
      type: POST_ERROR,
      payload: { msg: error.response.data.msg, status: error.response.status }
    });
  }
};

// Delete Comment
export const deleteComment = (postId, commentId) => async dispatch => {
  try {
    const res = await axios.delete(`/api/posts/comment/${postId}/${commentId}`);

    dispatch({
      type: REMOVE_COMMENT,
      payload: commentId
    });

    dispatch(setAlert('Comment Removed', 'success'));
  } catch (error) {
    dispatch({
      type: POST_ERROR,
      payload: { msg: error.response.data.msg, status: error.response.status }
    });
  }
};

1 Ответ

1 голос
/ 01 апреля 2020

Вы обновляете массив posts, который используется для визуализации PostItems в Feed.

case UPDATE_LIKES:
  return {
    ...state,
    posts: state.posts.map(post =>
      post._id === payload.postId ? { ...post, likes: payload.likes } : post
    ),
    loading: false
  };

Однако в Post.js вы используете объект Post , а не массив Posts. Post не был обновлен действием UPDATE_LIKES, поэтому ваш компонент не рендерится повторно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...