reactjs - 根据 Google Maps API 结果更改表数据集
问题描述
我正在尝试实现一个过滤器,它允许我根据位置与其地址之间的距离过滤用户。使用 useMemo 向表提供数据,基本上是这样的:
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]
);
这很好用,在控制台中,结果按我的预期返回。但是,我的表没有更新。我怀疑这是因为 API 调用的结果是异步的,因此在结果出现之前重新渲染了表。
我曾尝试使用 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
完成后运行。所以我的日志看起来像这样(我目前有 16 个联系人/用户):
- 日志:在 useEffect()
- 日志:状态已经改变,过滤将开始
- (16) 日志:即将返回 shouldReturn 值
- 日志:计算距离:1324
- 日志:计算距离:4326
- ...
所以基本上,它仍然忽略了我的函数的异步状态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
总是会返回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])
}
推荐阅读
- devops - Puppet 副本文件(如果不为空)
- python - serial_asyncio:将串行端口读入队列并从 main() 读取该队列不起作用
- angular - 错误 Angular Uncaught TypeError:无法在引导时读取未定义的属性“绑定”
- python - Python df CSV如何删除默认行号
- java - 如何从活动访问服务的变量
- reactjs - React.js MERN 应用程序:Axios 删除路由未读取道具(范围问题)
- java - 网关 + 后端服务的 OAuth2 流
- java - 如何使用 compareTo 按字典顺序打印 3 个数字和字母中的最大值
- javascript - 通过函数创建对象并使用方法而不将对象存储在变量中
- ruby-on-rails - 如何设计两个不同的门户?