乐观的用户界面更新-反应

时间:2020-06-21 17:29:25

标签: reactjs redux react-state-management

我想这是React的基础,但是我不确定如何使它正常工作,基本上,当我删除,创建或编辑组件中的任何内容时,我希望更改实时进行而无需刷新页面,我已经通过搜索功能在某种程度上实现了这一目标,但不能完全确定如何使用删除功能,例如:

这是我正在使用的工具,如何将其与axios删除功能一起使用? 谢谢

import { connect } from 'react-redux';
import { fetchTournaments } from '../actions/tournaments';
import Item from './Item';
import EditTournament from './EditTournament';
import axios from 'axios';

import '../styles/Item.css';

class SearchAndDisplay extends React.PureComponent {
  componentDidMount() {
    this.props.fetchTournaments();
  }

  state = {
    searchCriteria: '',
    isLoading: false
  };

  handleChange = event => {
    this.setState({
      searchCriteria: event.target.value
    });
  };

  async handleDelete(id) {
    const url = `http://localhost:4000/tournaments/`;

    await axios
      .delete(url + id)
      .then(res => {
        console.log(res.data);
      })
      .catch(err => {
        console.log(err);
      });
  }

  formatDate(date) {
    let options = {
      year: 'numeric',
      month: 'numeric',
      day: 'numeric',
      hour: 'numeric',
      minute: 'numeric',
      second: 'numeric',
      hour12: false
    };

    let newDate = new Date(Date.parse(date));
    let format = new Intl.DateTimeFormat('default', options).format(newDate);

    return format;
  }

  handleChange = event => {
    this.setState({ searchCriteria: event.target.value });
  };

  renderList() {
    let tournmentsArray = this.props.tournaments;

    const filterTournaments = tournmentsArray.filter(item =>
      item.name.includes(this.state.searchCriteria)
    );

    if (filterTournaments === undefined || filterTournaments.length === 0) {
      return (
        <React.Fragment>
          <div className="notFound">
            Something went wrong.
            <br />
            <button
              className="notFoundButton"
              onClick={() => {
                this.setState({ searchCriteria: '' });
              }}
            >
              Retry
            </button>
          </div>
        </React.Fragment>
      );
    } else {
      return filterTournaments.map(item => (
        <Item
          key={item.name}
          name={item.name}
          organizer={item.organizer}
          participants={Object.values(item.participants)}
          game={item.game}
          start={this.formatDate(item.startDate)}
        >
          <div className="buttonBar">
            <EditTournament id={item.id} />
            <button
              className="button"
              onClick={() => {
                if (
                  window.confirm('Are you sure you want to delete this item?')
                ) {
                  this.handleDelete(item.id);
                }
              }}
            >
              Delete
            </button>
          </div>
        </Item>
      ));
    }
  }

  render() {
    return (
      <div className="container">
        <input
          onChange={this.handleChange}
          className="input"
          placeholder="Search..."
          id="searchField"
          value={this.state.searchCriteria}
        />
        <div className="row">{this.renderList()}</div>
      </div>
    );
  }
}

function mapStateToProps({ tournaments }) {
  return {
    tournaments: Object.values(tournaments).flat()
  };
}

export default connect(mapStateToProps, {
  fetchTournaments
})(SearchAndDisplay);

与删除创建和编辑数据不同,它由redux处理,如下所示:

创建比赛:

import { reduxForm, Field } from 'redux-form';
import '../styles/promptForms.css';
import '../styles/Header.css';
import { connect } from 'react-redux';
import { createTournaments } from '../actions/tournaments';

class CreateTournamentPromptFrom extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      showHide: false
    };
  }
  createTournamentButton() {
    return (
      <div>
        <button
          className="genericButton"
          onClick={() => this.setState({ showHide: true })}
        >
          CREATE TOURNAMENT
        </button>
      </div>
    );
  }

  renderInput = ({ input, label }) => {
    return (
      <div>
        <label>{label}</label>
        <br />
        <input className="promptInput" {...input} autoComplete="off" />
      </div>
    );
  };

  onSubmit = formValues => {
    this.props.createTournaments(formValues);
  };

  render() {
    const { showHide } = this.state;

    return (
      <React.Fragment>
        <div className={`overlay ${showHide ? 'toggle' : ''} `} />

        <div className={`promptBox ${showHide ? 'toggle' : ''} `}>
          <h3>localhost:3000 says</h3>
          <form onSubmit={this.props.handleSubmit(this.onSubmit)}>
            <Field
              name="name"
              component={this.renderInput}
              label="Enter Tournament:"
            />

            <button className="okayButton">OK</button>
          </form>
          <button
            className="cancelButton"
            onClick={() => this.setState({ showHide: false })}
          >
            Cancel
          </button>
        </div>
        {this.createTournamentButton()}
      </React.Fragment>
    );
  }
}

const formWrapped = reduxForm({
  form: 'promptForm'
})(CreateTournamentPromptFrom);

export default connect(null, { createTournaments })(formWrapped);

动作:

import {
  FETCH_TOURNAMENTS,
  FETCH_TOURNAMENT,
  CREATE_TOURNAMENT,
  EDIT_TOURNAMENT
} from './types';

import { API_TOURNAMENTS_URL } from '../constants/api';
import axios from 'axios';

export const fetchTournaments = () => async dispatch => {
  const response = await axios.get(API_TOURNAMENTS_URL);

  dispatch({
    type: FETCH_TOURNAMENTS,
    payload: response.data.flat()
  });
};

export const fetchTournament = id => async dispatch => {
  const response = await axios.get(`http://localhost:4000/tournaments/${id}`);

  dispatch({ type: FETCH_TOURNAMENT, payload: response.data });
};

export const createTournaments = formValues => async dispatch => {
  const response = await axios.post(API_TOURNAMENTS_URL, {
    ...formValues
  });

  dispatch({ type: CREATE_TOURNAMENT, payload: response.data });
};

export const editTournaments = (id, formValues) => async dispatch => {
  const response = await axios.patch(
    `http://localhost:4000/tournaments/${id}`,
    formValues
  );

  dispatch({ type: EDIT_TOURNAMENT, payload: response.data });
};

减速器:

import _ from 'lodash';

import {
  FETCH_TOURNAMENT,
  CREATE_TOURNAMENT,
  FETCH_TOURNAMENTS,
  EDIT_TOURNAMENT,
  DELETE_TOURNAMENT
} from '../actions/types';

export default (state = {}, action) => {
  switch (action.type) {
    case FETCH_TOURNAMENT:
      return { ...state, [action.payload.id]: action.payload };
    case FETCH_TOURNAMENTS:
      return { ...state, [action.payload.id]: action.payload };
    case CREATE_TOURNAMENT:
      return { ...state, [action.payload.id]: action.payload };
    case EDIT_TOURNAMENT:
      return { ...state, [action.payload.id]: action.payload };
    case DELETE_TOURNAMENT:
      return _.omit(state, action.payload);
    default:
      return state;
  }
};

2 个答案:

答案 0 :(得分:1)

这里是使用forceUpdate()的方法(因为您不想使用状态):

import React, { Component } from 'react';
import { render } from 'react-dom';

class App extends Component {
  constructor() {
    super();
      this.items = [
        {id: 1, name: "item 1"},
        {id: 2, name: "item 2"},
      ];

    this.handleDelete = this.handleDelete.bind(this);
  }

  handleDelete(index) {
    const newState = [...this.items];
    delete newState[index];
    // or
    // newState.splice(index, 1);
    this.items = newState;
    this.forceUpdate();
  }

  render() {
    return (
      <div>
        {
          this.items.map((item, index) => {
            return (
              <div>
                {item.name}
                <button onClick={() => this.handleDelete(index)}>Delete item</button>
              </div>
            )
          })
        }
      </div>
    );
  }
}

render(<App />, document.getElementById('root'));

只需在map方法中传递索引: ...map((item, index) => ...);

在axios调用后在then()中进行操作。

请注意,文档强烈建议您在可能的情况下避免使用forceUpdate(),因此您确实应该为此使用状态,我看不出有任何好的理由不这样做在这里使用它。

这里是quick repro on Stackblitz

答案 1 :(得分:1)

要“乐观地”从状态中删除项目,您需要立即从状态中删除该项目以立即在UI中反映更改。 ,您将需要添加额外的redux状态,以“保留”后端的待删除操作。删除成功后,清除保留的删除,如果删除失败,则清除保留的删除并将其重新添加到常规数据中(并可能显示一些错误消息或吐司等。)。

我看到您没有通过redux进行删除,因此请使用本地组件状态,并且在渲染时必须过滤锦标赛数据。

class SearchAndDisplay extends PureComponent {
  componentDidMount() {
    this.props.fetchTournaments();
  }

  state = {
    searchCriteria: "",
    isLoading: false,
    optimisticTournaments: null // <-- state to hold temp "deleted" data
  };

  handleChange = event => {
    this.setState({
      searchCriteria: event.target.value
    });
  };

  async handleDelete(id) {
    console.log("delete id", id);
    // optimistically remove element
    this.setState({
      optimisticTournaments: this.props.tournaments.filter(
        item => item.id !== id
      )
    });

    await axios
      .delete(url + id)
      .then(res => {
        console.log(res.data);
        // Need to create a call back to let parent know element was deleted
        this.props.deleteSuccess(id);
      })
      .catch(err => {
        console.log(err);
        alert("Failed to delete");
      })
      .finally(() => {
        this.setState({ optimisticTournaments: null });
      });
  }

  formatDate(date) {
    let options = {
      year: "numeric",
      month: "numeric",
      day: "numeric",
      hour: "numeric",
      minute: "numeric",
      second: "numeric",
      hour12: false
    };

    let newDate = new Date(Date.parse(date));
    let format = new Intl.DateTimeFormat("default", options).format(newDate);

    return format;
  }

  handleChange = event => {
    this.setState({ searchCriteria: event.target.value });
  };

  renderList() {
    let tournmentsArray =
      this.state.optimisticTournaments || this.props.tournaments;

    const filterTournaments = tournmentsArray.filter(item =>
      item.name.includes(this.state.searchCriteria)
    );

    if (filterTournaments === undefined || filterTournaments.length === 0) {
      return (
        <React.Fragment>
          <div className="notFound">
            Something went wrong.
            <br />
            <button
              className="notFoundButton"
              onClick={() => {
                this.setState({ searchCriteria: "" });
              }}
            >
              Retry
            </button>
          </div>
        </React.Fragment>
      );
    } else {
      return filterTournaments.map(item => (
        <Item
           key={item.name}
           name={item.name}
           organizer={item.organizer}
           participants={Object.values(item.participants)}
           game={item.game}
           start={this.formatDate(item.startDate)}
         >
          <div className="buttonBar">
            <EditTournament id={item.id} />
            <button
              className="button"
              onClick={() => {
                if (
                  window.confirm("Are you sure you want to delete this item?")
                ) {
                  this.handleDelete(item.id);
                }
              }}
            >
              Delete
            </button>
          </div>
        </Item>
      ));
    }
  }

  render() {
    return (
      <div className="container">
        <input
          onChange={this.handleChange}
          className="input"
          placeholder="Search..."
          id="searchField"
          value={this.state.searchCriteria}
        />
        <div className="row">{this.renderList()}</div>
      </div>
    );
  }
}

Edit optimistic delete