Как обновить объект массива в редукторе - PullRequest
1 голос
/ 27 апреля 2019

TLDR: Как обновить объект массива в редукторе

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

Сообщения извлекаются из действия, передаются и отображаются как опора сообщения.В идеале он должен создать новый объект лайков в upvote

. Пользователь может щелкнуть лайк, а в бэкэнде он добавляет лайк.И это хорошо.Внешний интерфейс должен повысить текущее значение до плюс +1, однако текущая логика не работает.

При получении этой ошибки с текущей логикой

похоже, что ошибка TypeError: Неверная попытка распространить не повторяемый экземпляр

console.log(index) отображает одинаковое количество для любой публикации, на которую нажал пользователь.

например, как

20

Я не смог бы использовать состояние, мне нужно было бы сделать это в избыточном.

https://i.stack.imgur.com/1N0Nh.png <- представление о том, как выглядит внешний интерфейс </p>

Вот структура сообщений

{
  "id": 5,
  "title": "React Interview Questiossssnsdd",
  "post_content": "ssss",
  "username": "blueowl",
  "createdAt": "2019-04-26T09:38:10.324Z",
  "updatedAt": "2019-04-26T18:55:39.319Z",
  "userId": 1,
  "Likes": [
    {
      "id": 131,
      "like": true,
      "createdAt": "2019-04-26T12:20:58.251Z",
      "updatedAt": "2019-04-26T12:20:58.251Z",
      "userId": 1,
      "postId": 5
    },
    {
      "id": 152,
      "like": true,
      "createdAt": "2019-04-26T14:01:13.347Z",
      "updatedAt": "2019-04-26T14:01:13.347Z",
      "userId": 1,
      "postId": 5
    },
    {
      "id": 153,
      "like": true,
      "createdAt": "2019-04-26T14:01:46.739Z",
      "updatedAt": "2019-04-26T14:01:46.739Z",
      "userId": 1,
      "postId": 5
    },...

Пример структуры Likes

[
  {
    "id": 182,
    "like": true,
    "createdAt": "2019-04-27T11:05:05.612Z",
    "updatedAt": "2019-04-27T11:05:05.612Z",
    "userId": 1,
    "postId": 5
  },
  {
    "id": 178,
    "like": true,
    "createdAt": "2019-04-27T10:44:49.311Z",
    "updatedAt": "2019-04-27T10:44:49.311Z",
    "userId": 1,
    "postId": 5
  },
  {
    "id": 179,
    "like": true,
    "createdAt": "2019-04-27T10:45:27.380Z",
    "updatedAt": "2019-04-27T10:45:27.380Z",
    "userId": 1,
    "postId": 5
  },
  {
    "id": 180,
    "like": true,
    "createdAt": "2019-04-27T10:46:44.260Z",
    "updatedAt": "2019-04-27T10:46:44.260Z",
    "userId": 1,
    "postId": 5
  },

редуктор

const initialState = {
    post: [],
    postError: null,
    posts:[],
    isEditing:false,
    isEditingId:null,
    likes:[],
    someLike:[],
    postId:null
}


export default (state = initialState, action) => {
    switch (action.type) {
    case GET_POSTS:
      console.log(action.data)
      return {
         ...state, 
        posts: action.data, // maps posts fine,
      }

    case ADD_LIKE:
        console.log(action.id) // renders post id
        // console.log(state.posts) // logs posts array
        console.log(state.posts)

        const index = state.posts.find((post) => post.id === action.id).Likes.length 
        console.log(index); // gets likes length for the corresponding id to whatever post that has been clickd
        // renders 5 or 3 (their is currently 2 posts)
        // honestly don't what im doing below this line of code but should make a new like object
        return [
        {
            Likes: [
                ...state.posts.find((post) => post.id === action.id).Likes.length + 1,
                action.newLikeObject
            ]
         }    
      ]  

здесь показано количество обновлений

myLikes={post.Likes.length} // right here

    render(){
        const {posts} = this.props;    // from reducer   
        return (
          <div>
            {posts.map(post => (

              <Paper key={post.id} style={Styles.myPaper}>
                <PostItem
                  myLikes={post.Likes.length} // right here
                  myTitle={this.state.title}
                  editChange={this.onChange}
                  editForm={this.formEditing}
                  isEditing={this.props.isEditingId === post.id}
                  removePost={this.removePost}
                  {...post}

                />
              </Paper>
            ))}
          </div>
        );
    }
}

дополнительная информация

actions.js

export const postLike = (id) => {
    return (dispatch) => {
        // console.log(userId);
        return Axios.post('/api/posts/like', {
            postId: id
        }).then( (like) => {

            dispatch({type: ADD_LIKE, id})
                // console.log('you have liked this', like)
        }).catch( (err)=> {
                console.log('there seem to be an error', err);
        })

    }
}

Редактировать

console.log(newState)

{
  "post": [],
  "postError": null,
  "posts": [
    {
      "id": 5,
      "title": "React Interview Questiossssnsdd",
      "post_content": "ssss",
      "username": "EliHood",
      "createdAt": "2019-04-26T09:38:10.324Z",
      "updatedAt": "2019-04-26T18:55:39.319Z",
      "userId": 1,
      "Likes": [
        {
          "id": 219,
          "like": true,
          "createdAt": "2019-04-27T15:54:03.841Z",
          "updatedAt": "2019-04-27T15:54:03.841Z",
          "userId": 1,
          "postId": 5
        },
        {
          "id": 189,
          "like": true,
          "createdAt": "2019-04-27T11:11:07.558Z",
          "updatedAt": "2019-04-27T11:11:07.558Z",
          "userId": 1,
          "postId": 5
        },
        {
          "id": 190,
          "like": true,
          "createdAt": "2019-04-27T11:12:09.599Z",
          "updatedAt": "2019-04-27T11:12:09.599Z",
          "userId": 1,
          "postId": 5
        },
        ....,
  "isEditing": false,
  "isEditingId": null,
  "likes": [
    77,
    24
  ],
  "someLike": [],
  "postId": null
}

Подобный компонент

import React, { Component } from 'react';
import ReactDOM from 'react-dom'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCoffee, faAdjust } from '@fortawesome/free-solid-svg-icons';
import {connect} from 'react-redux';
import {  postLike} from '../actions/';
class Like extends Component{
    constructor(props){
        super(props);
        this.state = {
            likes: null,
            heart: false
        }
    }

    //  passes post id thats stored in PostItem.js
    clickLike = (id) => {
        this.props.postLike(id);
        // toggles between css class
        this.setState({
            heart: !this.state.heart
        })
    }
    render(){
       return(
            <div style={{float:'right', fontSize: '1.5em', color:'tomato'}} >
            <i style={{ marginRight: '140px'}} className={this.state.heart ? 'fa fa-heart':'fa fa-heart-o' }>
                    <span style={{ marginLeft: '6px'}}>
                        <a href="#" onClick={() =>this.clickLike(this.props.like)}>Like</a>   

                    </span>
                    {/* gets the like counts */}
                    <span style={{ marginLeft: '7px'}} >{this.props.likes}  </span>  

                </i>
            </div>       
       )
    }
}
const mapStateToProps = (state) => ({
    isEditingId: state.post.isEditingId,

})
const mapDispatchToProps = (dispatch) => ({

    postLike: (id) => dispatch( postLike(id))
    // Pass id to the DeletePost functions.
});
export default connect(mapStateToProps, mapDispatchToProps)(Like);

Подобный компонент, передаваемый здесь как <Like like={id} likes={myLikes} />

PostItem.js

import React, { Component } from 'react';
import Paper from '@material-ui/core/Paper';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
import moment from 'moment';
import Editable from './Editable';
import {connect} from 'react-redux';
import {UpdatePost, postLike, getCount} from '../actions/';
import Like from './Like';
import Axios from '../Axios';
const Styles = {
    myPaper: {
        margin: '20px 0px',
        padding: '20px'
    },
    button:{
        marginRight:'30px'
    }
}
class PostItem extends Component{
    constructor(props){
        super(props);
        this.state = {
            disabled: false,
            myId: 0,
            likes:0
        }
    }

    componentWillMount(){

    }
    onUpdate = (id, title) => () => {
        // we need the id so expres knows what post to update, and the title being that only editing the title. 
        if(this.props.myTitle !== null){
            const creds = {
                id, title
            }
            this.props.UpdatePost(creds); 
        }
    }

    render(){
        const {title, id, userId, removePost, createdAt, post_content, username, editForm, isEditing, editChange, myTitle, postUpdate, Likes, clickLike, myLikes} = this.props
        return(
            <div>
                   <Typography variant="h6" component="h3">
                   {/* if else teneray operator */}
                   {isEditing ? (
                          <Editable editField={myTitle ? myTitle : title} editChange={editChange}/>
                   ): (
                       <div>
                           {title}
                       </div>    
                   )}         
                   </Typography>
                   <Typography  component={'span'} variant={'body2'}>
                       {post_content}
                       <h5>by: {username} </h5>
                       {/*  component span cancels out the cant be a decedent of error */}
                       <Typography  component={'span'} variant={'body2'} color="textSecondary">{moment(createdAt).calendar()}</Typography>
                      {/* gets like counts */}
                       <Like like={id} likes={myLikes} /> 
                   </Typography>
                   {!isEditing ? (
                       <Button variant="outlined" type="submit" onClick={editForm(id)}>
                           Edit
                       </Button>
                   ):(     
                       // pass id, and myTitle which as we remember myTitle is the new value when updating the title
                        <div>
                            <Button 
                                disabled={myTitle.length <= 3}
                                variant="outlined" 
                                onClick={this.onUpdate(id, myTitle)}>
                                Update
                            </Button>
                            <Button 
                                variant="outlined" 
                                style={{marginLeft: '0.7%'}}
                                onClick={editForm(null)}>
                                Close
                            </Button>
                        </div>
                   )}
                   {!isEditing && (
                    <Button
                        style={{marginLeft: '0.7%'}}
                        variant="outlined"
                        color="primary"
                        type="submit"
                        onClick={removePost(id)}>
                        Remove
                    </Button>
                    )}
           </div>
       )
    }
}
const mapStateToProps = (state) => ({
    isEditingId: state.post.isEditingId,

})
const mapDispatchToProps = (dispatch) => ({
    // pass creds which can be called anything, but i just call it credentials but it should be called something more 
    // specific.
    UpdatePost: (creds) => dispatch(UpdatePost(creds)),
    postLike: (id) => dispatch( postLike(id)),

    // Pass id to the DeletePost functions.
});
export default connect(mapStateToProps, mapDispatchToProps)(PostItem);

Posts.js (основной родитель)

import React, { Component } from 'react';
import PostList from './PostList';
import {connect} from 'react-redux';
import { withRouter, Redirect} from 'react-router-dom';
import {GetPosts} from '../actions/';
const Styles = {
    myPaper:{
      margin: '20px 0px',
      padding:'20px'
    }
    , 
    wrapper:{
      padding:'0px 60px'
    }
}
class Posts extends Component {
  state = {
    posts: [],
    loading: true,
    isEditing: false, 
    // likes:[]
  }
  async componentWillMount(){
    await this.props.GetPosts();

    const thesePosts = await this.props.myPosts
    const myPosts2 = await thesePosts
    // const filtered = myPosts2.map((post) => post.Likes )
    // const likesCount = filtered.map( (like) => like.length)
    this.setState({
      posts: myPosts2,
      loading:false
    })



  }


  render() {
    const {loading} = this.state;
    const { myPosts} = this.props
    if (!this.props.isAuthenticated) {
      return (<Redirect to='/signIn' />);
    }
    if(loading){
      return "loading..."
    }
    return (
      <div className="App" style={Styles.wrapper}>
        <h1> Posts </h1>
        <PostList posts={this.state.posts}/>
      </div>
    );
  }
}
const mapStateToProps = (state) => ({
  isAuthenticated: state.user.isAuthenticated,
  myPosts: state.post.posts,


})
const mapDispatchToProps = (dispatch, state) => ({
  GetPosts: () => dispatch( GetPosts())
});
export default withRouter(connect(mapStateToProps,mapDispatchToProps)(Posts));

PostList.js

import React, { Component } from 'react';
import Paper from '@material-ui/core/Paper';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
import moment from 'moment';
import {connect} from 'react-redux';
import {DeletePost, postLike, UpdatePost,EditChange, GetPosts,  getCount, DisableButton} from '../actions/';
import PostItem from './PostItem';
import _ from 'lodash';
const Styles = {
    myPaper: {
        margin: '20px 0px',
        padding: '20px'
    }
}
class PostList extends Component{
    constructor(props){
        super(props);
        this.state ={
            title: '',
            loading:true,
            posts:[],

        }
    } 

    componentWillMount(){
        this.props.GetPosts();
        const ourPosts = this.props.myPosts
       this.setState({
         posts: ourPosts,
         loading:false
       })

       console.log(this.state.posts)
     }
     componentWillReceiveProps(nextProps) {
        const hasNewLike = false;
        if(this.state.posts && this.state.posts.length) {
          for(let index=0; index < nextProps.myPosts.length; index++) {
          if(nextProps.myPosts[index].Likes.length != 
           this.state.posts[index].Likes.length) {
             hasNewLike = true;

          }
        }
      }
      if(hasNewLike) {
       this.setState({posts: nextProps.myPosts});  // here we are updating the posts state if redux state has updated value of likes
      }
      console.log(nextProps.myPosts)
     }

    // Return a new function. Otherwise the DeletePost action will be dispatch each
     // time the Component rerenders.
    removePost = (id) => () => {
        this.props.DeletePost(id);
    }

    onChange = (e) => {
        e.preventDefault();
        this.setState({
            title: e.target.value
        })
    }
    formEditing = (id) => ()=> {;
        this.props.EditChange(id);
    }



    render(){
        // const {posts, ourLikes, likes} = this.props;

        // console.log(posts)
        // console.log(this.props.ourLikes);
        return (
          <div>
            {this.state.posts.map(post => (

              <Paper key={post.id} style={Styles.myPaper}>
                <PostItem
                  myLikes={post.Likes.length} // right here
                  myTitle={this.state.title}
                  editChange={this.onChange}
                  editForm={this.formEditing}
                  isEditing={this.props.isEditingId === post.id}
                  removePost={this.removePost}
                  {...post}

                />
              </Paper>
            ))}
          </div>
        );
    }
}
const mapStateToProps = (state) => ({
    isEditingId: state.post.isEditingId,
    myPosts: state.post.posts,
    // ourLikes: state.post.likes // reducer likes 
})
const mapDispatchToProps = (dispatch) => ({
    // pass creds which can be called anything, but i just call it credentials but it should be called something more 
    // specific.
    EditChange: (id) => dispatch(EditChange(id)),
    UpdatePost: (creds) => dispatch(UpdatePost(creds)),
    postLike: (id) => dispatch( postLike(id)),
    GetPosts: () => dispatch( GetPosts()),

    // Pass id to the DeletePost functions.
    DeletePost: (id) => dispatch(DeletePost(id))
});
export default connect(mapStateToProps, mapDispatchToProps)(PostList);

Ответы [ 2 ]

0 голосов
/ 27 апреля 2019

Ошибка возникает из-за кода ниже

...state.posts.find((post) => post.id === action.id).Likes.length + 1

, поэтому здесь мы находим длину лайков , результатом которой будет числои затем мы пытаемся распространить переменную числового типа, но оператор spread (...) работает для итераций как объект, массив.

Из того, что я понимаюмы хотим обновить массив likes в коллекции posts .

case ADD_LIKE:
        const newState = {...state};  // here I am trying to shallow  copy the existing state
        newState.posts.find(post => post.id == action.id).Likes.push(action.newLikeObject); // here we are trying to append the new like object to already existing **likes** array in the **posts** which should now make the count increase by 1

        return newState;

Если мы хотим использовать оператор распространения для обновления массива, мы можем использовать, как показано ниже:

case ADD_LIKE:
        const newState = {...state};  // here I am trying to shallow  copy the existing state;
        const existingLikesOfPost = newState.posts.find(post => post.id == action.id).Likes;
        newState.posts.find(post => post.id == action.id).Likes = [...existingLikesOfPost, action.newLikeObject]; // using this approach I got some code duplication so I suggested the first approach of using **push** method of array.
       return newState;

В Posts.js мы можем добавить другой метод жизненного цикла, как показано ниже:

 componentWillReceiveProps(nextProps) {
   const hasNewLike = false;
   if(this.state.posts && this.state.posts.length) {
     for(let index=0; index < nextProps.myPosts.length; index++) {
     if(nextProps.myPosts[index].Likes.length != 
      this.state.posts[index].Likes.length) {
        hasNewLike = true;

     }
   }
 }
 if(hasNewLike) {
  this.setState({posts: nextProps.myPosts});  // here we are updating the posts state if redux state has updated value of likes
 }
}

отредактированное выше решение для использования componentWillrecieveProps вместо getDerivedStateFromProps

0 голосов
/ 27 апреля 2019

В настоящее время вы пытаетесь распространить целое число со следующей строкой:

...state.posts.find((post) => post.id === action.id).Likes.length + 1,

(вам не следует пытаться модифицировать свойство length массива напрямую, как это, если вы пытались это сделатьделать)

Модификация глубоко вложенных объектов, таких как это, довольно раздражает без такой библиотеки, как ramda, но я думаю, что вы ищете что-то подобное в вашем выражении возврата:


// copy your state's posts
const newPosts = [...state.posts]

// Find the post you're adding a like to
const idx = newPosts.findIndex((post) => post.id === action.id)
const postToReplace = newPosts[idx]

// Replace that post with a copy...
newPosts[idx] = {
    ...postToReplace,
    // ... with the Likes object also copied, with the new Like appended.
    Likes: [
        ...postToReplace.Likes,
        action.newLikeObject
    ]
}

return {
    ...state,
    posts: newPosts
}

По сути, вам нужно углубиться в свой объект и начать заменять элементы, на которые вы воздействуете, неизменным образом.

...