react-native-elements icon indicating copy to clipboard operation
react-native-elements copied to clipboard

React Native 0.78 ListItem.Accordion Each child in a list should have a unique "key" prop.

Open FTW-001 opened this issue 8 months ago • 5 comments

Is there an existing issue for this?

  • [x] I have searched the existing issues

Explain what you did

I directly copied the following code in React Native 0.78 version

Image

Expected behavior

Image

Image

“Each child in a list should have a unique "key" prop. Check the render method of View. It was passed a child from PadView.”

Describe the bug

I have already set the key, but this error still occurred
“Each child in a list should have a unique "key" prop. Check the render method of View. It was passed a child from PadView.”

Steps To Reproduce

![Image](https://github.com/user-attachments/assets/16de3cf6-36af-4139-bc0c-46d39203231c)

![Image](https://github.com/user-attachments/assets/1fbdbabb-42cc-4829-bbf7-8d706b11be72)

“Each child in a list should have a unique "key" prop. Check the render method of `View`. It was passed a child from PadView.”

Screenshots

No response

Your Environment

Global Dependencies:

No related dependency found

Local Dependencies:

  • @rneui/base : ^4.0.0-rc.8
  • @rneui/themed : ^4.0.0-rc.8
  • react : 18.3.1
  • react-native : 0.78.0
  • @types/react : ^19

FTW-001 avatar Mar 07 '25 06:03 FTW-001

@rneui/base : ^4.0.0-rc.8 @rneui/themed : ^4.0.0-rc.8 react : 19 react-native : 0.78.0 @types/react : ^19

FTW-001 avatar Mar 07 '25 06:03 FTW-001

I upgraded my project from rn0.68 to rn0.78 along with rneui upgrade and have same issue.

0x13b avatar Mar 08 '25 13:03 0x13b

It seems if multiple items are under <ListItem>, the warning is displayed. Here are simplified demonstrations of the issue. To demonstrate the problem, I added keys at all levels. Example 1:

<FlatList
	keyExtractor={(item) => item.itemID}
	data={[{itemID: "aaaa"}]}
	renderItem={({ item }) => {
		return (
		<ListItem 			key={"item" + item.itemID}     >
			<ListItem.Title 	key={"title" + item.itemID}	>{"bcd"}</ListItem.Title>
			<ListItem.Subtitle 	key={"Subtitle" + item.itemID}>{"bcd"}</ListItem.Subtitle>
		</ListItem>);
	}}
/>

Example 2:

<FlatList
	keyExtractor={(item) => item.itemID}
	data={[{itemID: "aaaa"}]}
	renderItem={({ item }) => {
		return (
		<ListItem 			key={"item" + item.itemID}>
			<Avatar 		key={"avatar" + item.itemID} 	title="abc" />
			<ListItem.Content 	key={"content" + item.itemID}></ListItem.Content>
		</ListItem>);
	}}
/>

Bilal-Abdeen avatar Apr 06 '25 23:04 Bilal-Abdeen

Quick fix with patch-package https://github.com/ds300/patch-package#readme

diff --git a/node_modules/@rneui/base/dist/ListItem/components/PadView.js b/node_modules/@rneui/base/dist/ListItem/components/PadView.js
index 0e90243..d352736 100644
--- a/node_modules/@rneui/base/dist/ListItem/components/PadView.js
+++ b/node_modules/@rneui/base/dist/ListItem/components/PadView.js
@@ -11,13 +11,30 @@ var __rest = (this && this.__rest) || function (s, e) {
 };
 import React, { useRef } from 'react';
 import { View } from 'react-native';
+
 export const PadView = (_a) => {
     var { children, pad, Component } = _a, props = __rest(_a, ["children", "pad", "Component"]);
     const _root = useRef(null);
     const length = React.Children.count(children);
     const Container = Component || View;
-    return (React.createElement(Container, Object.assign({}, props, { ref: _root, testID: "RNE__LISTITEM__padView" }), React.Children.map(children, (child, index) => child && [
-        child,
-        index !== length - 1 && React.createElement(View, { style: { paddingLeft: pad } }),
-    ])));
+    
+    return (
+        React.createElement(
+            Container, 
+            Object.assign({}, props, { ref: _root, testID: "RNE__LISTITEM__padView" }), 
+            React.Children.map(children, (child, index) => 
+                child && [
+                    React.cloneElement(child, { key: `child-${index}` }),
+                    index !== length - 1 && 
+                        React.createElement(
+                            View, 
+                            { 
+                                style: { paddingLeft: pad }, 
+                                key: `separator-${index}` 
+                            }
+                        ),
+                ]
+            )
+        )
+    );
 };
\ No newline at end of file

claudiozam avatar Apr 13 '25 19:04 claudiozam

@FTW-001 This library is no longer maintained and if you wish you can use fork of this library where this issues is fixed and also some other issues are also fixed there.

Fork Repo React Native Vikalp Elements

Migration Guide

Documentation

deepktp avatar May 07 '25 17:05 deepktp

@FTW-001 This library is no longer maintained and if you wish you can use fork of this library where this issues is fixed and also some other issues are also fixed there.

Fork Repo React Native Vikalp Elements

Migration Guide

Documentation

There was a post from one of the mods in this thread (https://github.com/react-native-elements/react-native-elements/discussions/3925) that says that they intend to revive it.

NewMediaInstructor avatar Jul 12 '25 19:07 NewMediaInstructor

I replaced ListItem.Accordion with your a custom Accordion component using Expo-compatible components like Pressable, Animated, and View.

// components/AccordionItem.tsx

import React, { useEffect, useRef } from 'react'
import {
    View,
    Text,
    Pressable,
    LayoutAnimation,
    Platform,
    UIManager,
    Animated,
} from 'react-native'

if (Platform.OS === 'android') {
    UIManager.setLayoutAnimationEnabledExperimental?.(true)
}

interface AccordionItemProps {
    title: string
    description: string
    isExpanded: boolean
    onPress: () => void
}

const AccordionItem: React.FC<AccordionItemProps> = ({
    title,
    description,
    isExpanded,
    onPress,
}) => {
    const rotateAnim = useRef(new Animated.Value(0)).current

    useEffect(() => {
        Animated.timing(rotateAnim, {
            toValue: isExpanded ? 1 : 0,
            duration: 200,
            useNativeDriver: true,
        }).start()
    }, [isExpanded, rotateAnim])

    const rotation = rotateAnim.interpolate({
        inputRange: [0, 1],
        outputRange: ['0deg', '180deg'],
    })

    const handlePress = () => {
        LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
        onPress()
    }

    return (
        <View className="bg-white border-b border-gray-200">
            <Pressable
                onPress={handlePress}
                className="flex-row items-center justify-between px-4 py-3"
                accessibilityRole="button"
                accessibilityState={{ expanded: isExpanded }}
            >
                <Text className="flex-1 text-base font-semibold text-black">
                    {title}
                </Text>
                <Animated.View style={{ transform: [{ rotate: rotation }] }}>
                    <Ionicons name="chevron-down" size={20} color="black" />
                </Animated.View>
            </Pressable>
            {isExpanded && (
                <View className="px-4 py-3 bg-gray-100">
                    <Text className="leading-6 text-black">{description}</Text>
                </View>
            )}
        </View>
    )
}

export default AccordionItem```

// in the component

``` const handleAccordionPress = (index: number) => {
        const newLoadingStates = loadingStates.map((_, i) => i === index)
        setLoadingStates(newLoadingStates)
        setExpandedIndex((prevIndex) => (prevIndex === index ? null : index))
        setTimeout(() => {
            setLoadingStates(loadingStates.map(() => false))

            // Check if the last accordion is expanded
            if (index === steps.length - 1 && scrollViewRef.current) {
                // Scroll to the end of the ScrollView
                scrollViewRef.current.scrollToEnd({ animated: true })
            }
        }, 100)
    }
 <View className="bg-white shadow-sm pb-[80px]">
                        {steps.map((item, index) => (
                            <AccordionItem
                                key={item.id}
                                title={item.title}
                                description={item.description}
                                isExpanded={expandedIndex === index}
                                onPress={() => handleAccordionPress(index)}
                            />
                        ))}
                    </View>```

mehmethanguven avatar Aug 07 '25 10:08 mehmethanguven

Seems like there was some additional formatting to the patch file in claudiozam's comment above. Here is a super minimal diff generated by patch-package - all it adds is the ", key: PadView-${index}" to the View component. This solved the issue for me on Accordion and also the @rneui/themed ListItem I was using elsewhere. Not 100% sure why this wasn't a problem with react 18 though.

diff --git a/node_modules/@rneui/base/dist/ListItem/components/PadView.js b/node_modules/@rneui/base/dist/ListItem/components/PadView.js
index 0e90243..3f743bd 100644
--- a/node_modules/@rneui/base/dist/ListItem/components/PadView.js
+++ b/node_modules/@rneui/base/dist/ListItem/components/PadView.js
@@ -18,6 +18,6 @@ export const PadView = (_a) => {
     const Container = Component || View;
     return (React.createElement(Container, Object.assign({}, props, { ref: _root, testID: "RNE__LISTITEM__padView" }), React.Children.map(children, (child, index) => child && [
         child,
-        index !== length - 1 && React.createElement(View, { style: { paddingLeft: pad } }),
+        index !== length - 1 && React.createElement(View, { style: { paddingLeft: pad }, key: `PadView-${index}` }),
     ])));
 };

ChromeQ avatar Sep 13 '25 09:09 ChromeQ