根据 Google 地图 API 结果更改 table 数据集

Change table data set based on Google Maps API results

我正在尝试实现一个过滤器,它允许我根据位置与其地址之间的距离来过滤用户。使用useMemo向table提供数据,基本上是这样的:

const data = useMemo(
  () =>
    contacts.filter(contact => {
      var shouldReturn = true;

      clientFilter.map((filter, i) => {
        if (filter.condition === 'max_10km') {
          const originAddress = `${contact['street']} ${contact['number']}, ${contact['zip']} ${contact['city']}, ${contact['country']}`;
          calculateDistance(originAddress, filter.value, function(distance) {
            console.log('distance is calculated: ', distance);
            if (distance > 10000) {
              console.log('distance is > for', contact['name']);
              shouldReturn = false;
            }
          });
        }
      });
      return shouldReturn;
    }),
  [clientFilter]
);

这工作正常,在控制台中结果 return 正如我所期望的那样。但是,我的 table 没有更新。我怀疑这是因为 API 调用的结果是异步的,因此 table 在结果进入之前重新呈现。

我曾尝试使用 useEffect 更新数据,但这使我进入了一个不断重新呈现的循环,因此超过了最大值 (Maximum update depth exceeded.)。

我该怎么办?我应该尝试异步功能吗?如果是这样,我如何才能等到所有承诺都得到解决才更新我的数据?

编辑 11 月 14 日 所以,我今天一直在进一步研究这个问题。我已经设法将过滤切换到 useEffect() 而不是 useMemo(),所以目前,它看起来像这样:

const [filteredContacts, setFilteredContacts] = useState(contacts);
const data = useMemo(() => filteredContacts, [filteredContacts]);
const [isLoading, setIsLoading] = useState(false);

useEffect(() => {
  console.log('Log: In useEffect()');
  if (!isLoading) {
    setIsLoading(true);
    (async () => {
      console.log('Log: State has changed, filtering will start');
      filterContacts().then(() => setIsLoading(false));
    })();
  }
}, [clientFilter]);

async function filterContacts() {
  setFilteredContacts(
    contacts.filter(contact => {
      var shouldReturn = true;

      clientFilter.map((filter, i) => {
        if (
          filter.condition === 'equal' &&
          contact[filter.field] != filter.value &&
          shouldReturn
        ) {
          shouldReturn = false;
        }

        if (filter.condition === 'max_10km' && shouldReturn) {
          const originAddress = `${contact['street']} ${contact['number']}, ${contact['zip']} ${contact['city']}, ${contact['country']}`;
          calculateDistance(originAddress, filter.value, async function(
            distance
          ) {
            console.log('Log: Distance is calculated: ', distance);
            if (distance > 10000) {
              console.log(
                'Log: Distance is further away for',
                contact['title']
              );
              shouldReturn = false;
            }
          });
        }
      });
      console.log('Log: About to return shouldReturn value');
      return shouldReturn;
    })
  );
}

现在,这适用于我的其他过滤器,但异步距离计算仍在 shouldReturn 的 return 完成后运行。所以我的日志看起来像这样(我目前有 16 contacts/users):

所以基本上,它仍然忽略了我函数的异步状态 calculateDistance。有什么想法吗?

编辑 15/11 也可能有用,这是我的 calculateDistance() 函数:

function calculateDistance(origin, destination, callback) {
  const google = window.google;
  const directionsService = new google.maps.DirectionsService();

  directionsService.route(
    {
      origin: origin,
      destination: destination,
      travelMode: google.maps.TravelMode.DRIVING
    },
    (result, status) => {
      if (status === google.maps.DirectionsStatus.OK) {
        callback(result.routes[0].legs[0].distance.value);
      } else {
        console.error('Google Maps API error: ', status);
        callback(null);
      }
    }
  );
}

编辑 18/11 在@Secret的建议下,我将代码改为:

  const shouldRemoveContact = await(filters, contact) = () => {
    for (const filter of filters) {
      if (
        filter.condition === 'equal' &&
        contact[filter.field] != filter.value
      ) {
        return true
      }

     if (filter.condition === 'max_10km') {
        const originAddress = `${contact['street']} ${contact['number']}, ${contact['zip']} ${contact['city']}, ${contact['country']}`

        // please update calculateDistance to return a promise
        const distance = await calculateDistance(originAddress, filter.value)

        return distance > 10000

      }

    return false
    }
  }
   


  async function filterContacts (filters, contact) {
    // for every contact, run them to shouldRemoveContact
    // since shouldRemoveContact is async, we use Promise.all
    // to wait for the array of removeables to be ready
    const shouldRemove = await Promise.all(
      contact.map(c => shouldRemoveContact(filters, c))
    )

    // use shouldRemove to check if contact should be removed
    // and voila!
    return contacts.filter((c, i) => !shouldRemove[i])
  }

这导致:

Syntax error: Unexpected reserved word 'await'.

上线:

const shouldRemoveContact = await(filters, contact) = () => {

您的错误是正确的 - 您的 calculateDistance 是异步的,因此您无法正确过滤数据。本质上,您的代码可以归结为:

  setFilteredContacts(
    contacts.filter(contact => {
      var shouldReturn = true;

      // THIS IS WHERE CALCULATE IS
      // but it doesn't do anything because it is async
      // so effectively, it does nothing like a comment

      console.log('Log: About to return shouldReturn value');
      return shouldReturn;
    })
  );

如您所见,您的联系人将永远不会被过滤掉,因为 shouldReturn 将始终 return true - 它永远不会在该功能的中间改变!

您可以做的是预先 calculate 可过滤列表,然后将其用于 运行 您的过滤器。像这样的东西(伪代码):

// given a contact and a list of filters
// asynchronously return if it should or should not be filtered
const shouldRemoveContact = async () => {}

// THEN, in the filterContacts part:

// let's generate an array of calls to shouldRemoveContact
// i.e. [true, true, false, true], where `true` means it should be removed:
// note that we use await Promise.all here, waiting for all the data to be finished.
const shouldRemove = await Promise.all(
  contact.map(c => shouldRemoveContact(filters, c))
)


// we then simply shouldRemove to filter it all out
return contacts.filter((c, i) => !shouldRemove[i])

总计:

   // given a list of filters and one contact
   // asynchronously returns true or false depending on wheter or not
   // the contact should be removed
   const shouldRemoveContact = async (filters, contact) => {
     for (const filter of filters) {
       if (
         filter.condition === 'equal' &&
         contact[filter.field] != filter.value
       ) {
         return true
       }

      if (filter.condition === 'max_10km') {
         const originAddress = `${contact['street']} ${contact['number']}, ${contact['zip']} ${contact['city']}, ${contact['country']}`

         // please update calculateDistance to return a promise
         const distance = await calculateDistance(originAddress, filter.value)

         return distance > 10000

, async function(
           distance
         ) {
           console.log('Log: Distance is calculated: ', distance);
           if (distance > 10000) {
             console.log(
               'Log: Distance is further away for',
               contact['title']
             );
             shouldReturn = false;
           }
         });
       }
     }

     return false
   }


   async function filterContacts (filters, contact) {
     // for every contact, run them to shouldRemoveContact
     // since shouldRemoveContact is async, we use Promise.all
     // to wait for the array of removeables to be ready
     const shouldRemove = await Promise.all(
       contact.map(c => shouldRemoveContact(filters, c))
     )

     // use shouldRemove to check if contact should be removed
     // and voila!
     return contacts.filter((c, i) => !shouldRemove[i])
   }