Реагируйте: Пользовательский компонент не представляет правильные данные - PullRequest
0 голосов
/ 05 мая 2018

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

    /* eslint-disable import/first */
import React, { Component } from 'react';
import PropTypes from 'prop-types';

import Grid from 'material-ui/Grid';
import Paper from 'material-ui/Paper';
import List, { ListItem, ListItemIcon, ListItemText } from 'material-ui/List';
import Button from 'material-ui/Button';
import Card, { CardActions, CardContent } from 'material-ui/Card';
import Typography from 'material-ui/Typography';
import TextField from 'material-ui/TextField';
import { LinearProgress } from 'material-ui/Progress';

import Modal from 'react-responsive-modal';

var Moment = require('moment');

import TaskActions from '../redux/TaskRedux'
import ProgressColumn from './Progress'
import TaskCard from './Card'
import AddTaskCard from './AddTaskCard'

import { connect } from 'react-redux'

import '../styles/main.css';


export class Container extends Component{

    constructor(props){
        super(props);

        this.state = {
            fetching: this.props.fetching,
            taskName: null,
            showError: false,
            openEditModal: false,
            editTaskName: null,
            showUpdateError: false,
            toBeUpdatedTask: null,
            progressTasks:[],
            tasks: []
        }

    }
    componentDidMount(){
        this.props.getTasks()
    }

    componentDidUpdate(prevProps, prevState) {
        if(prevProps !== this.props){
            this.setState({fetching: this.props.fetching, 
            tasks: this.props.tasks})
        }
    }

    render(){
        let {fetching, progressTasks} = this.state
        let {tasks} = this.props
        if(tasks)
            tasks.map(i => console.log(i.title))
        return (
            <div className="grid-root">

                <Grid container spacing={24}>
                    <Grid item xs={3} sm={3}>
                        <Paper className="task-list-view">
                            <List className="task-list">
                                {tasks && tasks.map((item,i) => (
                                    <ListItem key={`item-${i}`}>
                                        <TaskCard task={item} key={`item-${i}`}/>
                                    </ListItem>
                                ))}
                            </List>
                            <AddTaskCard/>
                        </Paper>
                        {fetching ? (<LinearProgress color="secondary" />): ('')}
                    </Grid>
                    <Grid item xs={3} sm={3}>
                        <Paper className="task-list-view">
                            <ProgressColumn progressTasks={progressTasks}/>
                        </Paper>
                    </Grid>
                </Grid>
            </div>
        )
    }
}


const mapStateToProps = (state) => {
    return {
      fetching: state.task.fetching,
      tasks: state.task.tasks,
    }
  }

  const mapDispatchToProps = (dispatch) => {
    return {
        getTasks: () => dispatch(TaskActions.fetchTasks()),
        deleteTask: (taskId) => dispatch(TaskActions.deleteTask(taskId)),
        updateTask: (taskId,title) => dispatch(TaskActions.updateTask(taskId,title)),
    }
  }

  export default connect(mapStateToProps, mapDispatchToProps)(Container)

Вот компонент карты:

/* eslint-disable import/first */

import React, { Component } from 'react';

import { connect } from 'react-redux'

import Card, { CardActions, CardContent } from 'material-ui/Card';
import Button from 'material-ui/Button';
import Typography from 'material-ui/Typography';

import TaskActions from '../redux/TaskRedux'
import EditModal from './EditModal'

var Moment = require('moment');


export class TaskCard extends Component{

    constructor(props){
        super(props)

        this.state = {
            openEditModal: false,
            editTaskName: null,
            task: this.props.task
        }

    }

    // handle edit of task name
    handleTaskEdit(task){
        console.log("edit task", task)
        // this.setState({openEditModal: true, editTaskName: task.title, toBeUpdatedTask: task})
        this.props.editTask(task)
    }

    // handle start task button click
    // Will push the tasks for progressTasks state
    handleStartTask(task){
        // this.setState({
        //     progressTasks: [...this.state.progressTasks, task]
        // })
    }

    // handle deletion of the task
    handleTaskDelete(taskId){
        console.log(taskId)
        this.props.deleteTask(taskId)
        // this.props.getTasks()
    }


    render(){
        let {task} = this.state
        let created = Moment(task.created).format("Do MMM YYYY")
        console.log("task", task)
        return (
            <Card className="task-card" key={task.id}>
                <EditModal/>
                <CardContent>
                    <Typography variant="headline" component="h2">
                        {task.title}
                    </Typography>
                    <Typography color="textSecondary">
                        {created}
                    </Typography>
                </CardContent>
                <CardActions>
                    <Button size="small" color="primary" onClick={this.handleTaskEdit.bind(this, task)}>Edit</Button>
                    <Button size="small" color="primary" onClick={this.handleStartTask.bind(this, task)}>Start Task</Button>
                    <Button size="small" color="secondary" onClick={this.handleTaskDelete.bind(this, task.id)}>Delete</Button>
                </CardActions>
            </Card>
        )
    }
}

const mapStateToProps = (state) => {
    return {

    }
  }

  const mapDispatchToProps = (dispatch) => {
    return {
        getTasks: () => dispatch(TaskActions.fetchTasks()),
        deleteTask: (taskId) => dispatch(TaskActions.deleteTask(taskId)),
        editTask: (currentTask) => dispatch(TaskActions.editTask(currentTask))
    }
  }

  export default connect(mapStateToProps, mapDispatchToProps)(TaskCard)

Когда я удаляю задачу сверху или снизу, она обновляется нормально, но когда я делаю это из середины, нижняя исчезает, а та, которая должна быть удалена, остается / показывает. Когда вы обновляете его, то, конечно, все в порядке, но вопрос в том, почему это происходит? У меня есть сага о редуксе, которая снова выбирает задачи после удаления, и я могу подтвердить, что реквизиты тоже получают правильные данные.

ОБНОВЛЕНИЕ 1

Итак, я попытался отладить его с моего конца. Похоже, TaskCard несколько кеширует реквизит. enter image description here

На изображении, где добавлено {item.title} и под ним находится тачкарта, оба имеют разные названия, но {item.title} верно.

Обновление 2

Исходя из ответа jmathew, я обновил ключ ListItem и TaskCard до item.id, поэтому удаление работает, но обновление по-прежнему не работает. Опять же, в соответствии с обновлением 1, он по-прежнему показывает неправильный заголовок, но {item.title} является правильным. Итак, эта часть кода теперь выглядит так:

                <List className="task-list">
                    {tasks && tasks.map((item,i) => (
                        <ListItem key={item.id}>
                            {item.title}
                            <TaskCard item={item} key= 
              {`item-${item.id}`}/>
                        </ListItem>
                    ))}
                </List>

Обновление 3

Новый компонент TaskCard:

/* eslint-disable import/first */

import React, { Component } from 'react';

import { connect } from 'react-redux'

import Card, { CardActions, CardContent } from 'material-ui/Card';
import Button from 'material-ui/Button';
import Typography from 'material-ui/Typography';

import TaskActions from '../redux/TaskRedux'
import EditModal from './EditModal'

var Moment = require('moment');


export class TaskCard extends Component{

    constructor(props){
        super(props)

        this.state = {
            openEditModal: false,
            editTaskName: null,
            item: this.props.item
        }

    }

    // handle edit of task name
    handleTaskEdit(task){
        console.log("edit task", task)
        // this.setState({openEditModal: true, editTaskName: task.title, toBeUpdatedTask: task})
        this.props.editTask(task)
    }

    // handle start task button click
    // Will push the tasks for progressTasks state
    handleStartTask(task){
        // this.setState({
        //     progressTasks: [...this.state.progressTasks, task]
        // })
    }

    // handle deletion of the task
    handleTaskDelete(taskId){
        // console.log(taskId)
        this.props.deleteTask(taskId)
        // this.props.getTasks()
    }

    componentDidUpdate(prevProps, prevState, snapshot){
        console.log("update ", prevProps, prevState)
    }

    render(){
        let {item} = this.state
        let created = Moment(item.created).format("Do MMM YYYY")
        console.log("item",item)
        return (
            <Card className="task-card" key={`task-${item.id}`}>
                <EditModal/>
                <CardContent>
                    <Typography variant="headline" component="h2">
                        {item.title}
                    </Typography>
                    <Typography color="textSecondary">
                        {created}
                    </Typography>
                </CardContent>
                <CardActions>
                    <Button size="small" color="primary" onClick={this.handleTaskEdit.bind(this, item)}>Edit</Button>
                    <Button size="small" color="primary" onClick={this.handleStartTask.bind(this, item)}>Start Task</Button>
                    <Button size="small" color="secondary" onClick={this.handleTaskDelete.bind(this, item.id)}>Delete</Button>
                </CardActions>
            </Card>
        )
    }
}

const mapStateToProps = (state) => {
    return {
    }
  }

  const mapDispatchToProps = (dispatch) => {
    return {
        getTasks: () => dispatch(TaskActions.fetchTasks()),
        deleteTask: (taskId) => dispatch(TaskActions.deleteTask(taskId)),
        editTask: (currentTask) => dispatch(TaskActions.editTask(currentTask))
    }
  }

  export default connect(mapStateToProps, mapDispatchToProps)(TaskCard)

enter image description here

Как вы можете видеть на картинке выше, заголовок рядом с карточкой является обновленным, который вызывается кнопкой редактирования, но это же не передается компоненту TaskCard. На скриншоте есть консольный вывод, там строка «update» показывает, что изменение не вызвало componentDidUpdate.

Ответы [ 2 ]

0 голосов
/ 05 мая 2018

Кажется, проблема в сочетании ключа и использования состояния в дочернем компоненте. Поскольку вы используете индекс в качестве ключа, если вы удаляете элемент из середины, отображение преобразования данных в индекс изменяется, и карты перерисовываются с разными подпорками, однако вы не обновляете состояние в taskCard, поэтому данные не изменяются. не меняется. установка состояния, которое напрямую выводится из реквизита, не является правильным способом, и если вы делаете это, вам также необходимо обновить состояние в ответ на изменение реквизита

Чтобы решить проблему, все, что вам нужно сделать, это перевести задачу из состояния

 export class TaskCard extends Component{

    constructor(props){
        super(props)

        this.state = {
            openEditModal: false,
            editTaskName: null,
        }

    }

    // handle edit of task name
    handleTaskEdit(task){
        console.log("edit task", task)
        // this.setState({openEditModal: true, editTaskName: task.title, toBeUpdatedTask: task})
        this.props.editTask(task)
    }

    // handle start task button click
    // Will push the tasks for progressTasks state
    handleStartTask(task){
        // this.setState({
        //     progressTasks: [...this.state.progressTasks, task]
        // })
    }

    // handle deletion of the task
    handleTaskDelete(taskId){
        console.log(taskId)
        this.props.deleteTask(taskId)
        // this.props.getTasks()
    }


    render(){
        let {task} = this.props;
        let created = Moment(task.created).format("Do MMM YYYY")
        console.log("task", task)
        return (
            <Card className="task-card" key={task.id}>
                <EditModal/>
                <CardContent>
                    <Typography variant="headline" component="h2">
                        {task.title}
                    </Typography>
                    <Typography color="textSecondary">
                        {created}
                    </Typography>
                </CardContent>
                <CardActions>
                    <Button size="small" color="primary" onClick={this.handleTaskEdit.bind(this, task)}>Edit</Button>
                    <Button size="small" color="primary" onClick={this.handleStartTask.bind(this, task)}>Start Task</Button>
                    <Button size="small" color="secondary" onClick={this.handleTaskDelete.bind(this, task.id)}>Delete</Button>
                </CardActions>
            </Card>
        )
    }
}

Однако использование уникального идентификатора предмета повысит производительность.

0 голосов
/ 05 мая 2018

Ваши симптомы соответствуют проблеме с key опорой на ListItem. Похоже, вы используете индекс в массиве, когда вам нужно что-то уникальное для вспомогательных данных.

Перед вызовом delete реагирует на VDOM с ключами 1, 2, 3, 4. Тогда вы нажмете удалить на 3. Теперь вызывается повторный рендер, и вы используете i для регенерации ключей. Новые ключи 1, 2, 3. Реагирует на разницу между предыдущим DOM и новым и видит, что только 4 отсутствует, и удаляет его.

Решение вашей проблемы с обновлением может быть решено одним из двух способов:

  1. Используйте реквизит вместо состояния в вашем рендере. Это предпочтительный Выбор как проще. let {item} = this.state> let {item} = this.props (согласно ответу Шубхам).

  2. Реализация componentDidUpdate. Если вы все еще хотите сохранить товар в указать, что вам нужно убедиться, что он обновляется, когда родительский компонент отправляет вашему компоненту новые реквизиты:

Это может выглядеть так:

componentDidUpdate(prevProps,prevState) {
    if(this.props.item != this.state.item) {
        this.setState( { item: this.props.item });
    }
}
...