简体   繁体   中英

How to sync the DOM and Database in a React Component/View

I have created a list component in React but having trouble with two glaring problems.

  1. While the item gets removed (from the database as well )it is only reflected upon a refresh
  2. You may have noticed the list # or the ID column doesn't subtract when items are removed from the list.

I am using PostgreSQL on the backend and Sequelize as my Object/Relational Mapper and React for my views/components.

I have provided a gif so you all can see what I mean.

Thanks in advance!

This is my code:

React: 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.state = 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>
    );
  }
}

My Reducer:

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);
      state.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;

And this is my Student Model:

'use strict';
var Sequelize = require('sequelize')
var db = require('../index.js')

//hasOne, hasMany, belongsTo, belongsToMany Sequelize methods

module.exports = db.define('student', {
  name: {
    type: Sequelize.STRING,
    allowNull: false,

  },

  email: {
    type: Sequelize.STRING,
    allowNull: false,
    unique: true,
    validate: {
      isEmail: true
    }
  },

  campus: {
    type: Sequelize.STRING,
    allowNull: false,

  }
})

在此处输入图片说明

I want to answer this but there is a bit to much that your missing in this. I think you need to place it in a GIT repo and get more help that way.

first thing you need to do is to create a proper store configuration/build. Something akin to the below...

import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import createLogger from 'redux-logger'
import baseMiddleware from 'redux/middleware/'
import reducer from 'redux/modules/reducer'
import { routerMiddleware } from 'react-router-redux'
import { persistState } from 'redux-devtools';

export default function configureStore(initialState, history) {
    const middleware = baseMiddleware.with(thunk, createLogger(), routerMiddleware(history))

    const store = createStore(
        reducer,
        initialState,
        compose(
            applyMiddleware(...middleware),
            window.devToolsExtension ? window.devToolsExtension() : f => f,
            persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))
        )
    )

    if (module.hot) {
        // Enable Webpack hot module replacement for reducers
        module.hot.accept('redux/modules/reducer', () => {
            const nextRootReducer = require('redux/modules/reducer').default
            store.replaceReducer(nextRootReducer)
        })
    }

    return store
}

This references two main files. One for my reducers and the other for my middleware. This is important because it fits into the S of SOLID

This is my middleware...

import api from './api'
import authenticationMiddleware from './authenticationMiddleware'
import analyticsMiddleware from './analyticsMiddleware'

const middleware = [ api, analyticsMiddleware, authenticationMiddleware ]

export default {
    with(){
        for(let i = 0; i < arguments.length; i++){
            middleware[middleware.length] = arguments[i];
        }

        return middleware
    },
    ...middleware
}

and reducers

import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';

import {reducer as form} from 'redux-form';

import authentication from './authentication'

const reducer = combineReducers({
    router: routerReducer,
    authentication
})

export default reducer

You can then pass in your store into your root component and use Provider higher order component to then add the Redux context to the object graph

import React, { Component, PropTypes } from 'react'
import { Provider } from 'react-redux'
import routes from '../routes'
import { Router } from 'react-router'

export default class Root extends Component {
  render() {
    const { store, history } = this.props
    return (
      <Provider store={store}>
        <div>
          <Router history={history} routes={routes} />
        </div>
      </Provider>
    )
  }
}

Root.propTypes = {
  store: PropTypes.object.isRequired,
  history: PropTypes.object.isRequired
}

Now how you pass in the store to the Root component is more on you and wether you choose to have history, i'll leave that last part up to you.

You will also need to start using the Connect function on your components to create containers

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM