reactjs - 反应:自定义组件不代表正确的数据
问题描述
我现在面临一个奇怪的问题。我有一个通过 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。
解决方案
问题似乎是子组件中键和状态的组合。由于您使用索引作为键,如果您从中间删除项目,索引到数据渲染映射会更改并且卡片会使用不同的道具重新渲染,但是,您不会更新 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 将提高性能。
推荐阅读
- mysql - SQL - 查询期间的条件
- javascript - 将符号导出为“module.exports”,但在 TypeScript 中保留类型声明
- python - 连接WiFi python的最简单方法
- python - Google Analytics API 分页循环未结束且未更新新请求中的 pageToken 值 - Python
- typescript - 当用户在 Angular 中删除数据库中的项目时更新 UI 的最佳方法
- c# - 当 MaxDegreeOfParallelism > 1 时,Parallel.For 的 localInit 和 localFinally 等效用于 TPL 数据流块
- laravel - Laravel 5.7 在刀片中显示来自公共存储的图像
- wildfly - 如何使用 java 11 部署wildfly 15?
- java - org.eclipse.jetty.server.ssl.SslSelectChannelConnector 在新的 Eclipse Jetty 罐子中丢失
- scala - Gatling tansformOption 类型不匹配