React Native购物车页面组件重复渲染问题排查求助
问题描述
在React Native的CartDetailsScreen页面中,导入了DropdownComponent组件,同时页面内部有自定义的RenderDropDown组件,用于展示两个不同数据列表的下拉选择框。RenderDropDown功能正常,但点击导入的DropdownComponent时,RenderDropDown的选中值会从下拉栏中消失。经排查,RenderDropDown的选中值仍保存在状态中,且点击外部下拉框会触发整个页面重渲染。最初使用同一个RenderDropDown实现三个下拉框时就出现该问题,更换为外部DropdownComponent后问题依旧。
相关代码
DropdownComponent.js
import { StyleSheet, View } from 'react-native' import React from 'react' import { Dropdown} from 'react-native-element-dropdown' import { COLORS } from '../constants' const DropdownComponent = ({text, extractorValue, external_Id, data:mainData, extraData, value, setValue, isFocus, setIsFocus,area=false, retailer=false, retailerC=false, extraStyling}) => { return ( <View style={[styles.container, {...extraStyling}]}> <Dropdown style={[styles.dropdown, isFocus && { borderColor: COLORS.blue90 }]} placeholderStyle={styles.placeholderStyle} selectedTextStyle={styles.selectedTextStyle} inputSearchStyle={styles.inputSearchStyle} iconStyle={styles.iconStyle} data={mainData} containerStyle={{ backgroundColor:COLORS.white10, borderRadius:10, marginTop:10, borderColor:COLORS.gray10, shadowColor:COLORS.blue90, shadowOffset:{ height:15, width: 15 }, elevation:19, }} search maxHeight={300} labelField={extractorValue ? extractorValue :"name"} valueField={external_Id ? external_Id :"name"} placeholder={!isFocus ? `Select ${text ? text : 'Item'}` : '...'} searchPlaceholder="Search..." value={value} onFocus={() => setIsFocus(true)} onBlur={() => setIsFocus(false)} onChange={item => { retailer ? retailerC ? ( setValue({...extraData, retailerClass:external_Id ? item[external_Id] : item.name}) ): ( ( area ? setValue({...extraData, area:external_Id ? item[external_Id] : item.name}) : setValue({...extraData, route:external_Id ? item[external_Id] : item.name}) ) ) : ( setValue(external_Id ? item[external_Id] : item.name) ) setIsFocus(false) }} /> </View> ) } export default DropdownComponent
CartDetailsScreen.js
const CartDetailsScreen = ({navigation}) => { const [loading, setLoading] = React.useState(false) const {cartItems, setCartItems } = useContext(mapContext) const { createOrders, createOrderStatus, distributors, setDistributors, getDistributors, schemes, setSchemes, getSchemes} = useContext(commonUrlsContext) useLayoutEffect(()=>{ navigation.setOptions({ headerShown:true, headerTitle:'CART DETAILS', ...HeaderStyles, headerLeft: () =>( <HeaderLeft navigation={navigation} /> ) }) },[]) const [valueSchemeFromDropDown, setValueSchemeFromDropDown] = React.useState('') React.useEffect(()=>{ getSchemes() getDistributors() return () =>{ setDistributors([]) setSchemes([]) } },[]) React.useEffect(()=>{ if(createOrderStatus){ ToastAndroid.show('Order created successfully', ToastAndroid.CENTER) navigation.dispatch( CommonActions.reset({ index: 0, routes: [ { name: 'OrderSuccessful' }, ], }) ); setTimeout(()=>{{navigation.navigate('OrderSuccessful'), setCartItems([])}},1000) } },[createOrderStatus]) function RenderDropDown({index, text, data, external_Id, extractorValue, width, qtyScheme, valueScheme}){ return( <View style={[styles.container, width && {width: width}]}> <Dropdown style={[styles.dropdown]} placeholderStyle={styles.placeholderStyle} selectedTextStyle={styles.selectedTextStyle} inputSearchStyle={styles.inputSearchStyle} iconStyle={styles.iconStyle} data={data} containerStyle={{ borderRadius:10, marginTop:10, borderColor:COLORS.gray10, shadowColor:COLORS.gray90, shadowOffset:{ height:10, width: 10 }, elevation:15, }} search maxHeight={300} labelField={extractorValue ? extractorValue :"name"} valueField={external_Id ? external_Id :"name"} placeholder={`Select ${text ? text : 'item'}`} searchPlaceholder="Search..." // value={qtyScheme ? cartItems[index]?.distributorName} onChange={item => { qtyScheme ? ( console.log(1), cartItems[index].qtyScheme = item[external_Id], setCartItems(cartItems) ) : ( console.log(2), cartItems[index].distributorName = item[extractorValue], cartItems[index].distributorId = item[external_Id], setCartItems(cartItems) ) }} /> </View> ) } const removeFromCart = (id) =>{ let updatedCart = cartItems.filter(product => product?.products?.Id !== id) setCartItems(updatedCart) ToastAndroid.show('Item Removed From the Cart.', ToastAndroid.SHORT) if(updatedCart.length === 0){ navigation.goBack() } } const submitHandler = () =>{ setLoading(true) createOrders(cartItems, valueSchemeFromDropDown) } const reduceHandler = (item, index) =>{ if(item.qty === 1){ let newResults = cartItems.filter(product => product.products.Id !== item.products.Id) setCartItems(newResults) if(newResults.length === 0){ navigation.goBack() } } else{ item.qty = item.qty - 1 let newResults = cartItems.filter(product => product.products.Id !== item.products.Id ? product : item) setCartItems(newResults) } } const [testValue, setTestValue] = React.useState('') const [testFocus, setTestFocus] = React.useState(false) const incrementHandler = (item, index) =>{ let newResults = cartItems.map(product => product.products.Id === item.products.Id ? {...item, qty: product.qty + 1} : product) setCartItems(newResults) } return ( <> { loading ? <RowCenter style={{flex:1, flexDirection: 'column', alignItems:'center'}}> <StyledText fontSize={'20px'}>{createOrderStatus ? 'Redirecting..' : 'Loading..'}</StyledText> </RowCenter> : <> <View style={{flex:0.8}}> <ScrollView showsVerticalScrollIndicator={false}> {cartItems?.map((item, index)=>{ return( <View key={item?.products?.Id} style={{flexDirection:'row', padding:13, borderRadius:10, margin:10, alignItems:'center', backgroundColor:COLORS.green20, ...customShadow }}> <View style={{width:'100%'}}> <StyledText>Product Name : {item?.products?.Name}</StyledText> <ExtraSpace /> {item?.products?.Description && <StyledText>Product Description : {item?.products?.Description?.length > 30 ? item?.products?.Description?.slice(0,30) + '...': item?.products?.Description }</StyledText> } <ExtraSpace /> <View style={{flexDirection:'row', justifyContent:'space-between', alignItems:'center', padding:10}}> <TouchableOpacity onPress={()=>removeFromCart(item?.products?.Id)} style={{flexDirection:'row', justifyContent:'center', alignItems:'center'}}> <SimpleLineIcons name="trash" size={15} color={COLORS.error} /> <StyledText color={COLORS.error}>Remove</StyledText> </TouchableOpacity> <View style={{width:'40%', flexDirection:'row', justifyContent:'space-between', alignItems:'center',}}> <TouchableOpacity onPress={()=>reduceHandler(item, index)}> <Feather name="minus-circle" size={22} color={COLORS.gray90} /> </TouchableOpacity> <StyledText>Qty : {item?.qty}</StyledText> <TouchableOpacity onPress={()=>incrementHandler(item, index)} > <Feather name="plus-circle" size={22} color={COLORS.gray90} /> </TouchableOpacity> </View> </View> <RenderDropDown index={index} text={'Distributor'} data={distributors?.response} external_Id='external_Id' extractorValue='name' /> {item.distributorName && <Label color={'#131821'}> Clone Selected : {item.distributorName}</Label> } <RenderDropDown id={item?.products?.id} index={index} text={'Quantity Scheme'} data={schemes?.response?.filter(scheme => scheme.Type__c === 'Quantity Scheme')} external_Id='Id' extractorValue='Name' qtyScheme={true}/> </View> </View> ) })} </ScrollView> </View> <View style={{flex:0.2, justifyContent:'center', alignItems:'center', borderWidth:1, borderTopLeftRadius:10, borderTopRightRadius:10, marginBottom:2, marginHorizontal:1, borderTopColor:'gray', backgroundColor:"transparent"}}> <DropdownComponent text={'Value Scheme'} data={schemes?.response?.filter(scheme => scheme.Type__c === 'Value Scheme')} external_Id='Id' extractorValue={'Name'} value={testValue} setValue={setTestValue} isFocus={testFocus} setIsFocus={setTestFocus} extraStyling={{width: '100%', marginTop:10, marginBottom:1}}/> {/* <RenderDropDown text={'Value Scheme'} data={valueScheme} external_Id='Id' extractorValue='Name' valueScheme={true}/> */} <TextButton onPress={()=>{cartItems?.length === 0 ? null : submitHandler()}} componentStyling={{width:'94%', padding:8, marginHorizontal:10, marginBottom:10, marginTop:1}} title='Place Order'/> </View> </> } </> ) }
解决方案
1. 给RenderDropDown绑定value属性
RenderDropDown中的Dropdown组件未绑定value,页面重渲染时无法读取已选中的状态值,导致显示为空。需要根据下拉类型绑定对应状态:
function RenderDropDown({index, text, data, external_Id, extractorValue, width, qtyScheme, valueScheme}){ return( <View style={[styles.container, width && {width: width}]}> <Dropdown // ...其他属性 // 新增value绑定,保留选中值 value={qtyScheme ? cartItems[index]?.qtyScheme : cartItems[index]?.distributorName} onChange={item => { // ...其他逻辑 }} /> </View> ) }
2. 修正状态更新方式
原代码直接修改cartItems[index].xxx属于直接修改状态,React无法可靠检测到变化。必须通过创建新数组/对象的方式更新状态:
onChange={item => { qtyScheme ? ( const updatedCart = [...cartItems]; updatedCart[index] = {...updatedCart[index], qtyScheme: item[external_Id]}; setCartItems(updatedCart) ) : ( const updatedCart = [...cartItems]; updatedCart[index] = { ...updatedCart[index], distributorName: item[extractorValue], distributorId: item[external_Id] }; setCartItems(updatedCart) ) }}
3. 优化组件稳定性(可选)
将RenderDropDown用useCallback包裹,避免页面重渲染时重复创建组件实例:
const RenderDropDown = React.useCallback(({index, text, data, external_Id, extractorValue, width, qtyScheme, valueScheme})=>{ // 组件内容同上 }, [cartItems, setCartItems])
内容的提问来源于stack exchange,提问作者faijan memon
相关产品推荐
相关产品推荐

