6

我正在尝试创建一个乐观的响应,其中 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 <- 这是一个演示它发生的视频。

4

1 回答 1

6

对于遇到同样问题的任何人,主要问题是我的更新/乐观响应都不正确。这里要提的是这个块:

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
            }
          });
        }

如果您将它与我的原始代码进行比较,您会发现,当我 writeQuery 时,这次我有变量。查看 apollo devtools,我看到添加了一个条目,只是一个带有错误变量的条目。所以这很容易解决。乐观的响应是正确的(模仿从我们的突变返回的有效负载)。错误的另一个方面是,我获取所有这些数据的查询最初有一个缓存和网络的获取策略,这意味着当我们接收到数据时我们缓存它,并且我们要求最新的数据。所以这将始终获取最新数据。我不需要那个,因此会有一点滞后,我只需要乐观响应。默认情况下,apollo 会先缓存,它会在缓存中查找数据,如果不存在,我们会通过网络获取它。与缓存更新和慢速网络搭配得很好。

于 2018-09-18T05:28:51.957 回答