首页 > 解决方案 > 反应:自定义组件不代表正确的数据

问题描述

我现在面临一个奇怪的问题。我有一个通过 rest 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)

当我从顶部或底部删除任务时,它会更新得很好,但是当我从中间删除时,底部的任务会消失,而要删除的任务仍然会保留/显示。当你刷新它时,当然一切都很好,但问题是,为什么会发生这种情况?我有一个 redux saga,它在删除操作后再次获取任务,我可以确认 props 也确实获得了正确的数据。

更新 1

所以,我试图从我的最后调试它。看起来 TaskCard 有点缓存道具。 在此处输入图像描述

{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

新的任务卡组件:

/* 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)

在此处输入图像描述

正如您在上图中看到的,卡片旁边的标题是更新的标题,由编辑按钮触发,但同样的内容不会传递给 TaskCard 组件。在屏幕截图中,有一个控制台输出,其中“update”行表明更改没有触发 componentDidUpdate。

标签: reactjsreduxreact-reduxreact-propsreact-state-management

解决方案


问题似乎是子组件中键和状态的组合。由于您使用索引作为键,如果您从中间删除项目,索引到数据渲染映射会更改并且卡片会使用不同的道具重新渲染,但是,您不会更新 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>
        )
    }
}

但是,使用唯一的项目 ID 将提高性能。


推荐阅读