带有乐观的拖放效果的乐观React Apollo ui滞后

时间:2018-09-17 02:48:24

标签: reactjs apollo react-apollo react-dnd optimistic-ui

我正在尝试创建一个乐观的响应,其中ui通过拖放数据立即更新(最小延迟和更好的用户体验)。但是我遇到的问题是它仍然滞后。

所以发生的事情是我希望从查询中获得一个区域和未分配区域的列表,unassignedZone是一个对象,其中包含城市,而zone是一个其中包含城市的区域列表。编写突变时,将其拖放后会返回新的重新排序区域列表。重新排序是通过区域对象“ DisplayOrder”上的字段完成的。逻辑将数字设置正确。问题是,当我尝试使用乐观的ui进行模仿和更新时,会出现一些滞后,就像它仍在等待网络一样。

我想要实现的大部分工作都发生在onDragEnd =()=> {...}函数中。

import React, { Component } from "react";
import { graphql, compose, withApollo } from "react-apollo";
import gql from "graphql-tag";
import { withState } from "recompose";
import { withStyles } from "@material-ui/core/styles";
import Select from "@material-ui/core/Select";
import MenuItem from "@material-ui/core/MenuItem";
import Input from "@material-ui/core/Input";
import Grid from "@material-ui/core/Grid";
import InputLabel from "@material-ui/core/InputLabel";
import Tabs from "@material-ui/core/Tabs";
import Tab from "@material-ui/core/Tab";
import AppBar from "@material-ui/core/AppBar";
import _ from "lodash";
import FormControl from "@material-ui/core/FormControl";
import move from "lodash-move";
import { Zone } from "../../Components/Zone";

const style = {
  ddlRight: {
    left: "3px",
    position: "relative",
    paddingRight: "10px"
  },
  ddlDrop: {
    marginBottom: "20px"
  },
  dropdownInput: {
    minWidth: "190px"
  }
};

class Zones extends Component {
  constructor(props) {
    super(props);
    this.state = {
      companyId: "",
      districtId: "",
      selectedTab: "Zones",
      autoFocusDataId: null,
      zones: [],
      unassignedZone: null
    };
  }

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

  handleTabChange = (event, selectedTab) => {
    this.setState({ selectedTab });
  };

  onDragStart = () => {
    this.setState({
      autoFocusDataId: null
    });
  };

  calculateLatestDisplayOrder = () => {
    const { allZones } = this.state;
    if (allZones.length === 0) {
      return 10;
    }
    return allZones[allZones.length - 1].displayOrder + 10;
  };

  updateCitiesDisplayOrder = cities => {
    let displayOrder = 0;
    const reorderedCities = _.map(cities, aCity => {
      displayOrder += 10;
      const city = { ...aCity, ...{ displayOrder } };
      if (city.ZonesCities) {
        city.ZonesCities.displayOrder = displayOrder;
      }
      return city;
    });
    return reorderedCities;
  };

  moveAndUpdateDisplayOrder = (allZones, result) => {
    const reorderedZones = _.cloneDeep(
      move(allZones, result.source.index, result.destination.index)
    );
    let displayOrder = 0;
    _.each(reorderedZones, (aZone, index) => {
      displayOrder += 10;
      aZone.displayOrder = displayOrder;
    });
    return reorderedZones;
  };

  /**
   * droppable id board represents zones
   * @param result [holds our source and destination draggable content]
   * @return
   */

  onDragEnd = result => {
    console.log("Dragging");
    if (!result.destination) {
      return;
    }
    const source = result.source;
    const destination = result.destination;
    if (
      source.droppableId === destination.droppableId &&
      source.index === destination.index
    ) {
      return;
    }

    const {
      zonesByCompanyAndDistrict,
      unassignedZoneByCompanyAndDistrict
    } = this.props.zones;
    // reordering column
    if (result.type === "COLUMN") {
      if (result.source.index < 0 || result.destination.index < 0) {
        return;
      }

      const { reorderZones, companyId, districtId } = this.props;
      const sourceData = zonesByCompanyAndDistrict[result.source.index];
      const destinationData =
        zonesByCompanyAndDistrict[result.destination.index];
      const reorderedZones = this.moveAndUpdateDisplayOrder(
        zonesByCompanyAndDistrict,
        result
      );
      console.log(reorderedZones);
      console.log(unassignedZoneByCompanyAndDistrict);
      reorderZones({
        variables: {
          companyId,
          districtId,
          sourceDisplayOrder: sourceData.displayOrder,
          destinationDisplayOrder: destinationData.displayOrder,
          zoneId: sourceData.id
        },
        optimisticResponse: {
          __typename: "Mutation",
          reorderZones: {
            zonesByCompanyAndDistrict: reorderedZones
          }
        },
        // refetchQueries: () => ["zones"],
        update: (store, { data: { reorderZones } }) => {
          const data = store.readQuery({
            query: unassignedAndZonesQuery,
            variables: {
              companyId,
              districtId
            }
          });

          store.writeQuery({
            query: unassignedAndZonesQuery,
            data: data
          });
        }
      });
      // this.setState({ zones: reorderedZones });
      // Need to reorder zones api call here
      // TODO: Elixir endpoint to reorder zones
    }
    return;
  };

  render() {
    const { selectedTab } = this.state;
    const {
      classes,
      companies,
      districts,
      companyId,
      districtId,
      setCompanyId,
      setDistrictId,
      zones
    } = this.props;
    const isDisabled = !companyId || !districtId;
    return (
      <Grid container spacing={16}>
        <Grid container spacing={16} className={classes.ddlDrop}>
          <Grid item xs={12} className={classes.ddlRight}>
            <h2>Company Zones</h2>
          </Grid>
          <Grid item xs={2} className={classes.ddlRight}>
            <FormControl>
              <InputLabel htmlFor="company-helper">Company</InputLabel>
              <Select
                value={companyId}
                onChange={event => {
                  setCompanyId(event.target.value);
                }}
                input={
                  <Input
                    name="companyId"
                    id="company-helper"
                    className={classes.dropdownInput}
                  />
                }
              >
                {_.map(companies.companies, aCompany => {
                  return (
                    <MenuItem
                      value={aCompany.id}
                      key={`companyItem-${aCompany.id}`}
                    >
                      {aCompany.name}
                    </MenuItem>
                  );
                })}
              </Select>
            </FormControl>
          </Grid>
          <Grid item xs={2} className={classes.ddlRight}>
            <FormControl>
              <InputLabel htmlFor="district-helper">District</InputLabel>
              <Select
                value={districtId}
                onChange={event => {
                  setDistrictId(event.target.value);
                }}
                input={
                  <Input
                    name="districtId"
                    id="district-helper"
                    className={classes.dropdownInput}
                  />
                }
              >
                {_.map(districts.districts, aDistrict => {
                  return (
                    <MenuItem
                      value={aDistrict.id}
                      key={`districtItem-${aDistrict.id}`}
                    >
                      {aDistrict.name}
                    </MenuItem>
                  );
                })}
              </Select>
            </FormControl>
          </Grid>
        </Grid>
        <Grid container>
          <AppBar position="static" color="primary">
            <Tabs value={selectedTab} onChange={this.handleTabChange}>
              <Tab value="Zones" label="Zone" disabled={isDisabled} />
              <Tab
                value="Pricing Structure"
                label="Pricing Structure"
                disabled={isDisabled}
              />
              <Tab value="Pricing" label="Pricing" disabled={isDisabled} />
              <Tab
                value="Student Pricing"
                label="Student Pricing"
                disabled={isDisabled}
              />
            </Tabs>
          </AppBar>
          {selectedTab === "Zones" &&
            zones &&
            zones.zonesByCompanyAndDistrict && (
              <Zone
                onDragStart={this.onDragStart}
                onDragEnd={this.onDragEnd}
                zones={_.sortBy(zones.zonesByCompanyAndDistrict, [
                  "displayOrder"
                ])}
                unassignedZone={zones.unassignedZoneByCompanyAndDistrict}
              />
            )}
          {selectedTab === "Pricing Structure" && <div>Pricing Structure</div>}
          {selectedTab === "Pricing" && <div>Pricing</div>}
          {selectedTab === "Student Pricing" && <div>Student Pricing</div>}
        </Grid>
      </Grid>
    );
  }
}

const companiesQuery = gql`
  query allCompanies {
    companies {
      id
      name
    }
  }
`;

const districtsQuery = gql`
  query allDistricts {
    districts {
      id
      name
    }
  }
`;

const unassignedAndZonesQuery = gql`
  query zones($companyId: String!, $districtId: String!) {
    unassignedZoneByCompanyAndDistrict(
      companyId: $companyId
      districtId: $districtId
    ) {
      name
      description
      displayOrder
      cities {
        id
        name
      }
    }

    zonesByCompanyAndDistrict(companyId: $companyId, districtId: $districtId) {
      id
      name
      description
      displayOrder
      basePrice
      zoneCities {
        displayOrder
        city {
          id
          name
        }
      }
    }
  }
`;

const reorderZones = gql`
  mutation(
    $companyId: String!
    $districtId: String!
    $sourceDisplayOrder: Int!
    $destinationDisplayOrder: Int!
    $zoneId: String!
  ) {
    reorderZones(
      companyId: $companyId
      districtId: $districtId
      sourceDisplayOrder: $sourceDisplayOrder
      destinationDisplayOrder: $destinationDisplayOrder
      zoneId: $zoneId
    ) {
      id
      __typename
      name
      description
      displayOrder
      basePrice
      zoneCities {
        displayOrder
        city {
          id
          name
        }
      }
    }
  }
`;

export default compose(
  withState("companyId", "setCompanyId", ""),
  withState("districtId", "setDistrictId", ""),
  graphql(unassignedAndZonesQuery, {
    name: "zones",
    skip: ({ companyId, districtId }) => !(companyId && districtId),
    options: ({ companyId, districtId }) => ({
      variables: { companyId, districtId },
      fetchPolicy: "cache-and-network"
    })
  }),
  graphql(companiesQuery, {
    name: "companies",
    options: { fetchPolicy: "cache-and-network" }
  }),
  graphql(districtsQuery, {
    name: "districts",
    options: { fetchPolicy: "cache-and-network" }
  }),
  graphql(reorderZones, {
    name: "reorderZones"
  }),
  withApollo,
  withStyles(style)
)(Zones);

https://drive.google.com/file/d/1ujxTOGr0YopeBxrGfKDGfd1Cl0HiMaK0/view?usp=sharing <-这是演示它发生的视频。

1 个答案:

答案 0 :(得分:1)

对于遇到相同问题的任何人,主要问题是我的update / optimisticResponse都不正确。这里要提到的是这个块:

update: (store, { data: { reorderZones } }) => {
          const {
            zonesByCompanyAndDistrict,
            unassignedZoneByCompanyAndDistrict
          } = store.readQuery({
            query: unassignedAndZonesQuery,
            variables: {
              companyId,
              districtId
            }
          });
          const reorderedZones = this.moveAndUpdateDisplayOrder(
            zonesByCompanyAndDistrict,
            result
          );
          store.writeQuery({
            query: unassignedAndZonesQuery,
            variables: { companyId, districtId },
            data: {
              unassignedZoneByCompanyAndDistrict,
              zonesByCompanyAndDistrict: reorderedZones
            }
          });
        }

如果将其与我的原始代码进行比较,您会发现当我编写查询时,这次有变量。通过使用apollo devtools,我看到添加了一个条目,只是其中一个带有错误的变量。因此,这很容易解决。乐观的反应是正确的(模仿了我们突变产生的有效载荷)。另一个错误的方面是,我用于获取所有这些数据的查询最初具有高速缓存和网络的获取策略,这意味着当我们接收到数据时便对其进行高速缓存,并要求提供最新数据。因此,这将始终获取最新数据。我不需要那个,因此有点滞后,我只需要optimisticResponse。默认情况下,apollo首先执行缓存,在缓存中查找数据,如果不在缓存中,我们将通过网络进行抓取。与缓存更新和缓慢的网络配合得很好。