【问题标题】:How do I get my view to update after an item was removed from my store/State in React with Redux如何在 React with Redux 中从我的商店/状态中删除项目后更新视图
【发布时间】:2017-09-04 18:05:52
【问题描述】:

我是 React 和 Redux 的新手,并且对管理状态和 React 的一般概念感到满意。我先说这一点,因为我可能需要在我所拥有的背景下找到可能的解决方案——基本上这样我才能更好地理解它。 :)

话虽如此,这是我的问题:

我在 React 中创建了一个列表/表单组件,但遇到了两个明显的问题。

当项目被数据库删除时, 它仅在刷新时反映在视图中 您可能已经注意到列表 # 或 ID 列在从列表中删除项目时没有减去。 我在后端使用 PostgreSQL,并将 Sequelize 作为我的对象/关系映射器,并为我的视图/组件使用 React。

我提供了一个 gif,所以你们都可以明白我的意思。

提前致谢!

这是我的代码:

反应:Student.js

import React, { Component } from "react";
import store from "../store";
import { deleteStudent } from "../reducers";

export default class Students extends Component {
  constructor(props) {
    super(props);
    this.state = store.getState();
    this.deleteStudent = this.deleteStudent.bind(this);
  }

  componentDidMount() {
    this.unsubscribe = store.subscribe(() => {
      this.setState(store.getState());
    });
  }

  componentWillUnmount() {
    this.unsubscribe();
  }

  deleteStudent(index) {
    store.dispatch(deleteStudent(index));
    this.setState(store.getState());
  }

  render() {
    var students = this.props.students;
    return (
      <div className="container">
        <div className="sixteen columns">
          <h1 className="remove-bottom">Students</h1>
          <h5>List of current students and their campus</h5>
          <hr />
        </div>
        <div className="sixteen columns">
          <div className="example">
            <div>
              <table className="u-full-width">
                <thead>
                  <tr>
                    <th>#</th>
                    <th>Name</th>
                    <th>Email</th>
                    <th>Campus</th>
                  </tr>
                </thead>
                <tbody>
                  {students.map(function(student, index) {
                    return (
                      <tr key={index}>
                        <td>
                          {student.id}
                        </td>
                        <td>
                          {student.name}
                        </td>
                        <td>
                          {student.email}
                        </td>
                        <td>
                          {student.campus}
                        </td>
                        <td>
                          <a
                            className="button button-icon"
                            onClick={() => {
                              console.log(student.id);
                              this.deleteStudent(student.id);
                            }}
                            key={index}
                          >
                            <i className="fa fa-remove" />
                          </a>
                        </td>
                      </tr>
                    );
                  }, this)}
                </tbody>
              </table>
            </div>
          </div>
        </div>
      </div>
    );
  }
}

StudentForm.js

import React, { Component } from "react";
import store from "../store";
import { postStudent } from "../reducers";

const blankFormState = {
  name: "",
  email: "",
  campus: ""
};

export default class StudentForm extends Component {
  constructor(props) {
    super(props);
    this.state = blankFormState;
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    const target = event.target;
    this.setState({
      [target.name]: target.value
    });
  }

  handleSubmit(event) {
    event.preventDefault();
    store.dispatch(postStudent(this.state));
    this.setState(blankFormState);
  }

  render() {
    return (
      <div className="container">
        <div className="row">
          <div className="twelve columns">
            <form onSubmit={this.handleSubmit}>
              <div className="row">
                <div className="four columns">
                  <label>Name</label>
                  <input
                    className="u-full-width"
                    type="text"
                    name="name"
                    value={this.state.name}
                    onChange={this.handleChange}
                  />
                </div>
                <div className="four columns">
                  <label>Email</label>
                  <input
                    className="u-full-width"
                    type="text"
                    name="email"
                    value={this.state.email}
                    onChange={this.handleChange}
                  />
                </div>
                <div className="four columns">
                  <label>Campus</label>
                  <input
                    className="u-full-width"
                    type="text"
                    name="campus"
                    value={this.state.campus}
                    onChange={this.handleChange}
                  />
                </div>
              </div>
              <input className="button-primary" type="submit" />
            </form>
          </div>
        </div>
      </div>
    );
  }
}

我的 reducer.js

import { combineReducers } from "redux";
import axios from "axios";

const logError = console.error.bind(console);

// INITIAL STATE

const initialState = {
  students: [],
  campuses: []
};

//ACTION CREATORS

const UPDATE_NAME = "UPDATE_NAME";
const ADD_STUDENT = "ADD_STUDENT";
const DELETE_STUDENT = "DELETE_STUDENT";
const GET_STUDENTS = "GET_STUDENTS";
const UPDATE_CAMPUS = "UPDATE_CAMPUS";
const GET_CAMPUS = "GET_CAMPUS";
const GET_CAMPUSES = "GET_CAMPUSES";

// ACTION CREATORS

export function updateName(name) {
  const action = {
    type: UPDATE_NAME,
    name
  };
  return action;
}

export function addStudent(student) {
  return {
    type: ADD_STUDENT,
    student
  };
}

export function scrubStudent(student) {
  return {
    type: DELETE_STUDENT,
    student
  };
}

export function getStudents(students) {
  const action = {
    type: GET_STUDENTS,
    students
  };
  return action;
}

export function updateCampus(campus) {
  const action = {
    type: UPDATE_CAMPUS,
    campus
  };
  return action;
}

export function getCampus(campus) {
  const action = {
    type: GET_CAMPUS,
    campus
  };
  return action;
}

export function getCampuses(campuses) {
  const action = {
    type: GET_CAMPUSES,
    campuses
  };
  return action;
}

//THUNK CREATORS

export function fetchStudents() {
  return function thunk(dispatch) {
    return axios
      .get("/api/students")
      .then(function(res) {
        return res.data;
      })
      .then(students => {
        dispatch(getStudents(students));
      })
      .catch(logError);
  };
}

export function postStudent(student) {
  return function thunk(dispatch) {
    return axios
      .post("/api/students", student)
      .then(function(res) {
        return res.data;
      })
      .then(function(newStudent) {
        return dispatch(addStudent(newStudent));
      })
      .catch(logError);
  };
}

export function deleteStudent(id) {
  // console.log("student", student);
  return function thunk(dispatch) {
    return axios
      .delete("/api/students" + "/" + id)
      .then(function(id) {
        return dispatch(scrubStudent(id));
      })
      .catch(function(err) {
        return console.error("Removing student: " + id + " unsuccessful", err);
      });
  };
}

export function fetchCampuses() {
  return function thunk(dispatch) {
    return axios
      .get("/api/campuses")
      .then(function(res) {
        return res.data;
      })
      .then(function(campuses) {
        return dispatch(getCampuses(campuses));
      })
      .catch(logError);
  };
}

export function postCampus(student) {
  return function thunk(dispatch) {
    return axios
      .post("/api/campuses", campus)
      .then(function(res) {
        return res.data;
      })
      .then(function(newCampus) {
        return dispatch(getCampus(newCampus));
      })
      .catch(logError);
  };
}

// REDUCER

const rootReducer = function(state = initialState, action) {
  var newState = Object.assign({}, state);

  switch (action.type) {
    case GET_STUDENTS:
      newState.students = state.students.concat(action.students);
      return newState;

    case ADD_STUDENT:
      newState.students = state.students.concat([action.student]);
      return newState;

    case DELETE_STUDENT:
      // console.log("action.student", action.student);
      // console.log("state", state);
      newState = state.students.filter(function(student) {
        return student.id !== action.id;
      });
      return newState;

    case GET_CAMPUSES:
      newState.campuses = state.campuses.concat(action.campuses);
      return newState;

    case GET_CAMPUS:
      newState.campuses = state.campuses.concat([action.campus]);
      return newState;

    default:
      return state;
  }
};

export default rootReducer;

这就是我安装StudentsStudentForm 的方式

import React, { Component } from "react";
import Students from "./Students";
import StudentForm from "./StudentForm";
import store from "../store";

import { fetchStudents } from "../reducers";

export default class StudentContainer extends Component {
  constructor(props) {
    super(props);
    this.state = store.getState();
  }

  componentDidMount() {
    store.dispatch(fetchStudents());
    this.unsubscribe = store.subscribe(() => this.setState(store.getState()));
  }

  componentWillUnmount() {
    this.unsubscribe();
  }

  render() {
    return (
      <div>
        <Students students={this.state.students} />
        <StudentForm />
      </div>
    );
  }
}

我的 store.js

import { createStore, applyMiddleware } from "redux";
import rootReducer from "./reducers";
import createLogger from "redux-logger"; // https://github.com/evgenyrodionov/redux-logger
import thunkMiddleware from "redux-thunk"; // https://github.com/gaearon/redux-thunk

export default createStore(
  rootReducer,
  applyMiddleware(thunkMiddleware, createLogger())
);

【问题讨论】:

  • 你能说明你如何以及在哪里安装Students 吗?
  • @Panther,是的,如果我这样做会很有帮助。我刚刚更新了它。
  • 酷。你也可以显示你的store.js 吗?
  • @Panther 当然。谢谢。
  • 你也可以告诉你为什么在Students渲染中使用var students = this.props.students;,同时你还获得了状态并在该组件中订阅它。为什么不直接使用它的 state 而不是依赖父组件的 props?

标签: javascript postgresql reactjs redux


【解决方案1】:

删除学生后,您正在调度动作,并且您将动作创建者scrubStudent 传递给调度。您正在该操作创建者中传递已删除学生的 ID。现在你定义你的动作创建者的方式是这样的

export function scrubStudent(student) {
  return {
    type: DELETE_STUDENT,
    student
  };
}

所以这个函数的返回值将是一个类似这样的对象

scrubStudent(5) // returns {type: "DELETE_STUDENT", student: 5}

但是在你的 reducer 中,你正在像这样比较 id

case DELETE_STUDENT:
      // console.log("action.student", action.student);
      // console.log("state", state);
      newState = state.students.filter(function(student) {
        return student.id !== action.id;
      });
      return newState;

在上面的代码中action.id 是未定义的。相反,学生 ID 保存为 action.student。因此,对于数组的所有元素,比较将返回 true。因此,每次所有元素都将包含在新状态中。所以试着像这样改变你上面的代码

case DELETE_STUDENT:
      // console.log("action.student", action.student);
      // console.log("state", state);
      newState = state.students.filter(function(student) {
        return student.id !== action.student;
      });
      return newState;

【讨论】:

  • 感谢您的帮助。但是视图没有更新。在做了console.log('action.id', action.id) 之后,我可以看到你是对的,我在那里比较了undefined。但事后我意识到action.student 上没有id 属性那么我们为什么要比较它呢?
  • 您正在传递学生的id,当您在后端结果返回后调度操作时从后端返回,例如return dispatch(scrubStudent(id));。因此,如果您的后端返回被删除学生的 ID,那么 action.student 将包含该 ID。
  • @AntonioPavicevac-Ortiz 我还看到你到处都在使用 store.getState() 。我建议使用 react-redux 库 [redux.js.org/docs/basics/UsageWithReact.html]。当您将组件与此库连接时,您的视图将在您的状态发生变化时自动更新。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-15
  • 2020-02-06
  • 2018-11-30
相关资源
最近更新 更多