从子元素更改状态时,React组件不会重新渲染

时间:2018-10-28 15:58:26

标签: javascript reactjs redux

当我有没有子组件的组件时,我所有的动作都可以正常工作,但是,当我添加组件时,当调度deleteTodo动作时,TodoList /不会改变,并且它现在也循环呈现,与更改TodoList的动作相同状态“过滤器”。我发现可能是因为变异,但我什至没有碰到我的减速器。其他动作不会发生。感谢任何帮助,谢谢!

TodoList类:

import React, { Component } from 'react';
import { Container, ListGroup, ListGroupItem, Button, Input } from 'reactstrap';
import { CSSTransition, TransitionGroup } from 'react-transition-group'
import { connect } from 'react-redux';
import { getTodos, addTodo, deleteTodo, editTodo, updateTime, completeTodo } from '../actions/todoActions';
import 'bootstrap/dist/css/bootstrap.css'
import moment from 'moment'
import Todo from './Todo'
import 'react-datepicker/dist/react-datepicker.css';
import AddTodo from './AddTodo'

class TodoList extends Component {

constructor() {

    super();
    this.state = {
        addForm: false,
        filter: 'all'
    }

}

componentDidMount() {
    const tick = 1000;
    setInterval(this.props.updateTime, tick)
    // this.props.getTodos();

}

editHandler = (id, name, description, shouldCompleteAt, importance, completedAt) => {
    this.clearForm();
    this.setState({
        id,
        name,
        description,
        shouldCompleteAt: shouldCompleteAt || moment(),
        importance,
        completedAt
    })
}

deleteHandler = (id) => {
    console.log('22')
    this.props.deleteTodo(id)
}

handleAdd = () => {
    this.clearForm();
    this.setState({
        addForm: true
    })
}

renderForm = () => {
    if (this.state.addForm) {
        return <AddTodo clear={this.clearForm} />
    }
}

handleComplete = (id) => {
    this.props.completeTodo(id);
}

handleFilterChange = e => {
    console.log(e)
    e.preventDefault();
    this.setState({
        filter: e.target.value
    })
}

clearForm = () => {
    this.setState({
        addForm: false
    })
}


renderTime = (shouldCompleteAt, completedAt) => {
    if (shouldCompleteAt === null || shouldCompleteAt === undefined || !moment(shouldCompleteAt).isValid()) {
        return null
    } else {
        return <div>Complete till <span className={"todo-complete-to " + (completedAt ? "green" : this.props.todos.time > moment(shouldCompleteAt) ? "red" : "black")}>{moment(shouldCompleteAt).format('Do MMMM h:mm')}</span></div>
    }
}

renderTodo = (todo) => {
    if (this.state.filter !== 'all' && todo.importance !== this.state.filter) {
        return null
    } else if (this.state.id !== undefined && this.state.id === todo.id) {
        return <AddTodo key={todo.id} clearForm={this.clearForm} todo={todo} />
    } else {
        return <Todo key={todo.id} todo={todo} time={this.props.time} />
    }
}

render() {
    console.log('1')
    return (
        <Container>
            <Input type="select"
                value={this.state.filter}
                onChange={this.handleFilterChange}>
                <option>all</option>
                <option>normal</option>
                <option>important</option>
                <option>very important</option>
            </Input>
            <ListGroup>
                <TransitionGroup className="todo-list">
                    {this.props.todos.todos.map((todo) => {
                        return (this.renderTodo(todo))
                    })}
                </TransitionGroup>
            </ListGroup>
            {this.renderForm(this.state.addForm, null, "add")}
            <Button
                className="addButton"
                color="dark"
                style={{ marginBottom: '2rem' }}
                onClick={() => {
                    this.handleAdd();
                }}
            >
                Add Todo
            </Button>

        </Container>
    )
}
}

const mapStateToProps = (state) => ({
    todos: state.todos,
    time: state.todos.time,
    filter: state.todos.filter
})

export default connect(mapStateToProps, { getTodos, deleteTodo, addTodo, editTodo, updateTime, completeTodo })(TodoList);

Todo类:

import React, { Component } from 'react';
import { ListGroupItem, Button } from 'reactstrap';
import { connect } from "react-redux";
import { CSSTransition } from 'react-transition-group'
import {deleteTodo, editTodo, completeTodo } from '../actions/todoActions';
import moment from 'moment';
import AddTodo from './AddTodo'

class Todo extends Component {
    constructor() {
        super();
        this.state = {
            edit: false
        }
}
editHandler = () => {
    this.setState({
        edit: true
    })
}

deleteHandler = (id) => {
    this.props.deleteTodo(id)
    this.forceUpdate()
}

handleAdd = () => {
    this.clearForm();
    this.setState({
        addForm: true
    })
}

renderForm = (addForm = false, todo = {}, type) => {
    if (type === 'add' && addForm) {
        return <AddTodo clearForm={this.clearForm} />
    } else if (type === 'edit' && addForm) {
        return <AddTodo clearForm={this.clearForm} todo={todo} />
    }
}

handleComplete = (id) => {
    this.props.completeTodo(id);
}

renderTime = (shouldCompleteAt, completedAt) => {
    if (shouldCompleteAt === null || shouldCompleteAt === undefined || !moment(shouldCompleteAt).isValid()) {
        return null
    } else {
        return <div>Complete till <span className={"todo-complete-to" + ' ' + (completedAt ? "green" : this.props.time > moment(shouldCompleteAt) ? "red" : "black")}>{moment(shouldCompleteAt).format('Do MMMM h:mm')}</span></div>
    }
}

clearEdit = () => {
    this.setState({
        edit: false
    })
}

renderTodo = () => {

    const {
        name,
        description,
        importance,
        shouldCompleteAt,
        completedAt,
        id
    } = this.props.todo;

    if (this.state.edit) {
        console.log('1')
        return <AddTodo todo={this.props.todo} clear={this.clearEdit} />
    } else {
        return <CSSTransition key={id} timeout={900} classNames="fade">
            <ListGroupItem>
                <div className="todo-header">
                    <div className="todo-title">
                        {name}
                    </div>

                    <div className="todo-importance">
                        {importance}
                    </div>
                </div>
                <hr />
                <div className="todo-body">
                    {description}
                </div>
                <hr />
                <div className="todo-footer">
                    <Button
                        className="remove-btn" color="danger"
                        size="sm"
                        onClick={
                            () => {
                                this.deleteHandler(id)
                            }
                        }>&times;</Button>
                    <div className="todo-time">
                        {this.renderTime(shouldCompleteAt, completedAt)}
                    </div>
                    <div className="change-buttons">
                        <Button
                            className="edit-btn" color="primary"
                            size="sm"
                            onClick={
                                () => {
                                    this.editHandler()
                                }
                            }>Edit</Button>
                        <Button
                            className="complete-btn"
                            color="success"
                            size="sm"
                            disabled={completedAt !== null && completedAt !== undefined}
                            onClick={
                                () => {
                                    this.handleComplete(id)
                                }

                            }
                        >Complete</Button>
                    </div>
                </div>
            </ListGroupItem>
        </CSSTransition>
    }
}

render() {
    return (
        this.renderTodo()
    );
}
}


export default connect(null, { deleteTodo, editTodo, completeTodo })(Todo);

减速器:

    import { GET_TODOS, DELETE_TODO, ADD_TODO, COMPLETE_TODO, EDIT_TODO, UPDATE_TIME } from '../actions/types';
import { classNamesShape } from 'reactstrap';
import moment from 'moment'

const initialState = {
    todos: [
        { id: 1, name: 'Homework', description: 'Do homework', importance: 'normal', shouldCompleteAt: moment(), completedAt: null },
        { id: 2, name: 'Milk', description: 'Buy milk', importance: 'important', shouldCompleteAt: moment(), completedAt: null },
        { id: 3, name: 'Music', description: 'Listen music', importance: 'very important', shouldCompleteAt: moment(), completedAt: null },
    ],
    time: moment(),
    filter: 'all'
}

export default function (state = initialState, action) {

    switch (action.type) {
        case GET_TODOS:
            return {
                ...state
            }
        case DELETE_TODO:
            console.log(action.payload)
            return {
                ...state,
                todos: state.todos.filter(todo => todo.id !== action.payload)
            }
        case ADD_TODO:
            return {
                ...state,
                todos: [...state.todos, action.payload]
            }
        case COMPLETE_TODO:
            return {
                ...state,
                todos: state.todos.map(todo => {
                    if (todo.id === action.payload) {
                        todo.completedAt = moment();
                    }
                    return todo;
                })
            }
        case EDIT_TODO:
            return {
                ...state,
                todos: state.todos.map(todo => {
                    if (todo.id === action.payload.id) {
                        todo = action.payload
                    }
                    return todo;
                })
            }
        case UPDATE_TIME:
            return {
                ...state,
                time: moment()
            }
        default:
            return state;
    }
}

UPD: 这是AddTodo类,所有调度都在这里工作:

    import React, { Component } from 'react';
import { ListGroupItem, Button, Input } from 'reactstrap';
import { CSSTransition } from 'react-transition-group'
import { connect } from 'react-redux';
import { addTodo, editTodo } from '../actions/todoActions';
import 'bootstrap/dist/css/bootstrap.css'
import uuid from 'uuid'
import moment from 'moment'
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';

class AddTodo extends Component {

    constructor() {
    super();
    this.state = {
        id: -1,
        name: '',
        description: '',
        shouldCompleteAt: null,
        importance: 'normal',
        completedAt: null
    }
}

componentDidMount() {
    if (this.props.todo) {
        this.setState({
            ...this.props.todo
        })
    }
}

deleteHandler = () => {
    this.props.clear();
}

handleNameChange = (e) => {
    e.preventDefault();
    this.setState({
        name: e.target.value
    })
}

handleImportanceChange = (e) => {
    e.preventDefault();
    this.setState({
        importance: e.target.value
    })
}

handleDescriptionChange = (e) => {
    e.preventDefault();
    this.setState({
        description: e.target.value
    })
}

handleDateChange = (e) => {
    this.setState({
        shouldCompleteAt: moment(e).isValid() ? moment(e) : null
    })
}

handleSubmit = () => {
    const todo = {
        id: this.state.id,
        name: this.state.name,
        description: this.state.description,
        shouldCompleteAt: moment(this.state.shouldCompleteAt),
        importance: this.state.importance,
        completedAt: this.completedAt
    }
    if (this.props.todo) {
        this.props.editTodo(todo)
    } else {
        todo.id = uuid()
        this.props.addTodo(todo)
    }
    this.props.clear()
}

renderDatepicker = () => {
    if (this.state.shouldCompleteAt !== null && this.state.shouldCompleteAt !== undefined) {
        return (<DatePicker className="todo-complete-to"
            selected={moment(this.state.shouldCompleteAt || '')}
            onChange={this.handleDateChange}
            showTimeSelect
            timeFormat="HH:mm"
            timeIntervals={5}
            dateFormat="DD/MM/YYYY HH:mm"
            timeCaption="time"
        />)
    } else {
        return (<DatePicker className="todo-complete-to"
            onChange={this.handleDateChange}
            showTimeSelect
            timeFormat="HH:mm"
            timeIntervals={5}
            dateFormat="DD/MM/YYYY HH:mm"
            timeCaption="time"
        />)
    }

}



render() {
    return (
        <CSSTransition timeout={900} classNames="fade">
            <ListGroupItem>
                <div className="todo-header">
                    <div className="todo-title">
                        <Input type="text"
                            value={this.state.name}
                            onChange={this.handleNameChange} />
                    </div>

                    <div className="todo-importance">
                        <Input type="select"
                            value={this.state.importance}
                            onChange={this.handleImportanceChange}>
                            <option>normal</option>
                            <option>important</option>
                            <option>very important</option>
                        </Input>
                    </div>
                </div>
                <hr />
                <div className="todo-body">
                    <Input type="textarea" value={this.state.description} onChange={this.handleDescriptionChange} />
                </div>
                <hr />
                <div className="todo-footer">
                    <Button
                        className="remove-btn" color="danger"
                        size="sm"
                        onClick={
                            () => {
                                this.deleteHandler(this.state.id)
                            }
                        }>&times;</Button>

                    <div className="todo-time">
                        Complete till
                    {this.renderDatepicker()}
                    </div>
                    <div className="change-buttons">
                        <Button
                            className="complete-btn" color="success"
                            size="sm"
                            onClick={
                                () => {
                                    this.handleSubmit()
                                }
                            }>Submit</Button>
                    </div>
                </div>
            </ListGroupItem>
        </CSSTransition>
    )
}
}

export default connect(null, { addTodo, editTodo })(AddTodo);

UPD :: 好的,我发现该循环是由我的setInterval引起的:) 但是删除元素后,List仍然不会重新渲染

UPD:

case DELETE_TODO:
        console.log(action.payload)
        console.log(state.todos[0].id)
        console.log(state.todos[0].id === action.payload)
        return {
            ...state,
            todos: state.todos.filter(todo => todo.id !== action.payload)
        }

删除第一个元素 结果是

    todoReducer.js:23 
ab6f802d-be6e-4b7c-8480-16ad4073630d
    todoReducer.js:24 
ab6f802d-be6e-4b7c-8480-16ad4073630d
    todoReducer.js:25 
true

完整的项目,如果有帮助的话: https://github.com/MikluhaMaclay/todo-react https://codesandbox.io/s/github/MikluhaMaclay/todo-react

UPD ::: 对我来说是意外的,但是从render删除ListGroup和TransitionGroup确实解决了我的问题!

1 个答案:

答案 0 :(得分:-1)

在您的TodoList类中尝试以下操作:

将您的constructor更新为使用todos开头的props

constructor(props) {
    super(props)
    this.state = {
      todos: props.todos,
      addForm: false,
      filter: 'all'
  }
}

要处理redux state的更改,请在componentDidMount之后添加:

static getDerivedStateFromProps(nextProps, prevState){
  if(nextProps.todos.length !== prevState.todos.length){
     return { todos: nextProps.todos};
  }
  else return null;
}

还使用todos中的state

<TransitionGroup className="todo-list">
    {this.state.todos.map((todo) => {
        return (this.renderTodo(todo))
    })}
</TransitionGroup>