【发布时间】:2020-03-13 21:25:53
【问题描述】:
我正在使用 React 和 Apollo Graphql 创建一个应用程序。我的应用程序的一部分包括向用户显示选项列表,以便他可以选择一个。一旦他选择了其中一个,其他选项就被隐藏了。
这是我的代码:
/**
* Renders a list of simple products.
*/
export default function SimplesList(props: Props) {
return (
<Box>
{props.childProducts
.filter(child => showProduct(props.parentProduct, child))
.map(child => (
<SingleSimple
key={child.id}
product={child}
menuItemCacheId={props.menuItemCacheId}
parentCacheId={props.parentProduct.id}
/>
))}
</Box>
);
}
以及实际的元素:
export default function SingleSimple(props: Props) {
const classes = useStyles();
const [ref, setRef] = useState(null);
const [flipQuantity] = useFlipChosenProductQuantityMutation({
variables: {
input: {
productCacheId: props.product.id,
parentCacheId: props.parentCacheId,
menuItemCacheId: props.menuItemCacheId,
},
},
onError: err => {
if (process.env.NODE_ENV !== 'test') {
console.error('Error executing Flip Chosen Product Quantity Mutation', err);
Sentry.setExtras({ error: err, query: 'useFlipChosenProductQuantityMutation' });
Sentry.captureException(err);
}
},
});
const [validateProduct] = useValidateProductMutation({
variables: { productCacheId: props.menuItemCacheId },
onError: err => {
if (process.env.NODE_ENV !== 'test') {
console.error('Error executing Validate Product Mutation', err);
Sentry.setExtras({ error: err, query: 'useValidateProductMutation' });
Sentry.captureException(err);
}
},
});
const refCallback = useCallback(node => {
setRef(node);
}, []);
const scrollToElement = useCallback(() => {
if (ref) {
ref.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}
}, [ref]);
const onClickHandler = useCallback(async () => {
await flipQuantity();
if (props.product.isValid !== ProductValidationStatus.Unknown) {
validateProduct();
}
scrollToElement();
}, [flipQuantity, props.product.isValid, validateProduct, scrollToElement]);
return (
<ListItem className={classes.root}>
<div ref={refCallback}>
<Box display='flex' alignItems='center' onClick={onClickHandler}>
<Radio
edge='start'
checked={props.product.chosenQuantity > 0}
tabIndex={-1}
inputProps={{ 'aria-labelledby': props.product.name! }}
color='primary'
size='medium'
/>
<ListItemText
className={classes.text}
primary={props.product.name}
primaryTypographyProps={{ variant: 'body2' }}
/>
<ListItemText
className={classes.price}
primary={getProductPrice(props.product)}
primaryTypographyProps={{ variant: 'body2', noWrap: true, align: 'right' }}
/>
</Box>
{props.product.chosenQuantity > 0 &&
props.product.subproducts &&
props.product.subproducts.map(subproduct => (
<ListItem component='div' className={classes.multiLevelChoosable} key={subproduct!.id}>
<Choosable
product={subproduct!}
parentCacheId={props.product.id}
menuItemCacheId={props.menuItemCacheId}
is2ndLevel={true}
/>
</ListItem>
))}
</div>
</ListItem>
);
}
我的问题是:一旦用户从列表中选择了一个元素,我想将窗口滚动到该元素,因为他将有多个列表可供选择,并且在选择它们时会迷失方向。但是我的组件正在使用这个流程:
1- 用户点击给定的简单元素。
2- 此点击触发一个异步突变,该突变选择此元素而不是其他元素。
3- 更新应用程序状态并重新创建列表中的所有组件(过滤掉未选择的组件并显示已选择的组件)。
4- 重新创建完成后,我想滚动到选定的组件。
问题是当 flipQuantity 数量突变完成执行时,我调用了 scrollToElement 回调,但它包含的 ref 用于未选择的元素,不再呈现在屏幕上,因为新元素将由SimplesList 组件重新创建。
如何在最新的组件上触发scrollIntoView 函数?
更新:
相同的代码,但带有useRef 钩子:
export default function SingleSimple(props: Props) {
const classes = useStyles();
const ref = useRef(null);
const [flipQuantity] = useFlipChosenProductQuantityMutation({
variables: {
input: {
productCacheId: props.product.id,
parentCacheId: props.parentCacheId,
menuItemCacheId: props.menuItemCacheId,
},
},
onError: err => {
if (process.env.NODE_ENV !== 'test') {
console.error('Error executing Flip Chosen Product Quantity Mutation', err);
Sentry.setExtras({ error: err, query: 'useFlipChosenProductQuantityMutation' });
Sentry.captureException(err);
}
},
});
const [validateProduct] = useValidateProductMutation({
variables: { productCacheId: props.menuItemCacheId },
onError: err => {
if (process.env.NODE_ENV !== 'test') {
console.error('Error executing Validate Product Mutation', err);
Sentry.setExtras({ error: err, query: 'useValidateProductMutation' });
Sentry.captureException(err);
}
},
});
const scrollToElement = useCallback(() => {
if (ref && ref.current) {
ref.current.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}
}, [ref]);
const onClickHandler = useCallback(async () => {
await flipQuantity();
if (props.product.isValid !== ProductValidationStatus.Unknown) {
validateProduct();
}
scrollToElement();
}, [flipQuantity, props.product.isValid, validateProduct, scrollToElement]);
return (
<ListItem className={classes.root}>
<div ref={ref}>
<Box display='flex' alignItems='center' onClick={onClickHandler}>
<Radio
edge='start'
checked={props.product.chosenQuantity > 0}
tabIndex={-1}
inputProps={{ 'aria-labelledby': props.product.name! }}
color='primary'
size='medium'
/>
<ListItemText
className={classes.text}
primary={props.product.name}
primaryTypographyProps={{ variant: 'body2' }}
/>
<ListItemText
className={classes.price}
primary={getProductPrice(props.product)}
primaryTypographyProps={{ variant: 'body2', noWrap: true, align: 'right' }}
/>
</Box>
{props.product.chosenQuantity > 0 &&
props.product.subproducts &&
props.product.subproducts.map(subproduct => (
<ListItem component='div' className={classes.multiLevelChoosable} key={subproduct!.id}>
<Choosable
product={subproduct!}
parentCacheId={props.product.id}
menuItemCacheId={props.menuItemCacheId}
is2ndLevel={true}
/>
</ListItem>
))}
</div>
</ListItem>
);
}
更新 2:
我按照 Kornflexx 的建议再次更改了我的组件,但它仍然无法正常工作:
export default function SingleSimple(props: Props) {
const classes = useStyles();
const ref = useRef(null);
const [needScroll, setNeedScroll] = useState(false);
useEffect(() => {
if (needScroll) {
scrollToElement();
}
}, [ref]);
const [flipQuantity] = useFlipChosenProductQuantityMutation({
variables: {
input: {
productCacheId: props.product.id,
parentCacheId: props.parentCacheId,
menuItemCacheId: props.menuItemCacheId,
},
},
onError: err => {
if (process.env.NODE_ENV !== 'test') {
console.error('Error executing Flip Chosen Product Quantity Mutation', err);
Sentry.setExtras({ error: err, query: 'useFlipChosenProductQuantityMutation' });
Sentry.captureException(err);
}
},
});
const [validateProduct] = useValidateProductMutation({
variables: { productCacheId: props.menuItemCacheId },
onError: err => {
if (process.env.NODE_ENV !== 'test') {
console.error('Error executing Validate Product Mutation', err);
Sentry.setExtras({ error: err, query: 'useValidateProductMutation' });
Sentry.captureException(err);
}
},
});
const scrollToElement = useCallback(() => {
if (ref && ref.current) {
ref.current.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}
}, [ref]);
const onClickHandler = useCallback(async () => {
await flipQuantity();
if (props.product.isValid !== ProductValidationStatus.Unknown) {
validateProduct();
}
setNeedScroll(true);
}, [flipQuantity, props.product.isValid, validateProduct, scrollToElement]);
return (
<ListItem className={classes.root}>
<div ref={ref}>
<Box display='flex' alignItems='center' onClick={onClickHandler}>
<Radio
edge='start'
checked={props.product.chosenQuantity > 0}
tabIndex={-1}
inputProps={{ 'aria-labelledby': props.product.name! }}
color='primary'
size='medium'
/>
<ListItemText
className={classes.text}
primary={props.product.name}
primaryTypographyProps={{ variant: 'body2' }}
/>
<ListItemText
className={classes.price}
primary={getProductPrice(props.product)}
primaryTypographyProps={{ variant: 'body2', noWrap: true, align: 'right' }}
/>
</Box>
{props.product.chosenQuantity > 0 &&
props.product.subproducts &&
props.product.subproducts.map(subproduct => (
<ListItem component='div' className={classes.multiLevelChoosable} key={subproduct!.id}>
<Choosable
product={subproduct!}
parentCacheId={props.product.id}
menuItemCacheId={props.menuItemCacheId}
is2ndLevel={true}
/>
</ListItem>
))}
</div>
</ListItem>
);
}
现在我收到此错误:
index.js:1375 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
【问题讨论】:
-
你可以使用 useRef(作为使用元素 ref 的正确钩子),然后更新你的代码示例吗?
-
是的,我尝试使用
useRef钩子,但结果是一样的。在这种情况下,我不知道如何在组件被选中后触发scrollIntoView。 -
您可以使用
const [scrollNeeded, setScrollNeeded] = useState(false)。将scrollToElement()替换为setScrollNeeded(true)。添加一个效果,当ref改变时,检查scrollNeeded是否为真,将scrollNeeded设置为假并调用scrollToElement -
我也试过了,但没用。问题是
SingleSimple组件总是由SimplesList重新创建,所以当我的状态改变时,SingleSimple组件被重新创建,这个新组件的scrollNeed总是false。
标签: javascript reactjs apollo react-apollo