简体   繁体   中英

Creating an Accordion in React Native with Collapsible

I created an accordion and I created some logic for it but when I look at my code it doesn't feel right. I don't know where to ask so I'm asking here.

So I created an accordion component and Implemented it on my screen. What is specific about this accordion is that when one accordion opens other one closes. So I used useState() that I forward as a prop to the accordion. I was also thinking to put useState() inside the accordion but I don't know how to close it when I click on the other accordion. So I created this.

I need tips on how to improve it or how to make it cleaner because right now when I look at the code it works but it doesn't look good.

//AccordionCompontent.js
import React from 'react'
import { StyleSheet, View, Text, Dimensions } from 'react-native';
import Collapsible from 'react-native-collapsible';
import { TouchableWithoutFeedback } from 'react-native-gesture-handler';

const Accordion = props => {


    return (
        <View>
            <TouchableWithoutFeedback onPress={props.onPress}>
            <View style={styles.accordionContainer}>
            <Text style={styles.accordionContainerTitle} >{props.title}</Text>
            </View>
            </TouchableWithoutFeedback>
            <Collapsible style={styles.accordionCollapsedContainer} collapsed={props.isCollapsed}>
                {props.accordionRender}
            </Collapsible>
        </View>
    );
}

export default Accordion;

const styles = StyleSheet.create({
    accordionContainer: {
        justifyContent:'center',
        borderBottomWidth: 1,
        borderBottomColor: '#fff',
       minHeight: Dimensions.get('window').height / 15
    },
    accordionCollapsedContainer: {
        borderBottomWidth: 1,
        borderBottomColor: '#fff'
    },
    accordionContainerTitle: {
        color: '#fff',
        fontSize: 16,
        marginLeft:10
    },

})
//InformationScreen.js
import React, { useCallback, useLayoutEffect, useState } from 'react'
import { Text, View, StyleSheet, TouchableWithoutFeedback } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons'
import BodyText from '../components/Atomic/BodyText';
import Accordion from '../components/Accordion/Accordion';


const InformationScreen = props => {

    const [isCollapsed, setIsCollapsed] = useState([true, true, true, true, true])

    const collapse = (id) => {
        let updatetIsCollapsed = [...isCollapsed]
        updatetIsCollapsed[id] = !updatetIsCollapsed[id]
        const filteredUpdateIsCollapse = updatetIsCollapsed.filter((value, index) => id !== index)
        filteredUpdateIsCollapse.forEach(element => {
            if (!element) {
                const index = filteredUpdateIsCollapse.indexOf(element)
                filteredUpdateIsCollapse[index] = true
            }
        });
        filteredUpdateIsCollapse.splice(id, 0, updatetIsCollapsed[id])
        setIsCollapsed(filteredUpdateIsCollapse)
    }


    useLayoutEffect(() => {
        props.navigation.setOptions({
            title: 'Information',
            headerStyle: {
                backgroundColor: '#000'
            },
            headerTitleStyle: {
                fontSize: 22,
                color: '#fff'
            },
            headerLeft: () => (
                <View></View>
            ),
            headerRight: () => (
                <TouchableWithoutFeedback onPress={() => {
                    props.navigation.popToTop()
                }}>
                    <View style={styles.iconContainer}>
                        <Icon name="home" size={30} color={'#fff'} />
                    </View>
                </TouchableWithoutFeedback>
            )
        })
    })
    return (
        <View style={styles.container}>

            <Accordion onPress={useCallback(() => {
                collapse(0)
            },
                [isCollapsed, setIsCollapsed],
            )} title={"Lorem"} isCollapsed={isCollapsed[0]} accordionRender={
                <View style={styles.accordionItemContainer}>
                    <View style={styles.accordionItemSpacing}>
                        <BodyText>Lorem Ipsum</BodyText>
                        <BodyText>Lorem Ipsum</BodyText>
                    </View>
                    <View>
                        <BodyText>Lorem ipsum</BodyText>
                        <BodyText>Lorem ipsum</BodyText>
                    </View>
                </View>
            } />
            <Accordion onPress={useCallback(() => {
                collapse(1)
            },
                [isCollapsed, setIsCollapsed],
            )} title={"Bla bla"} isCollapsed={isCollapsed[1]} accordionRender={
                <View>
                    <Text>Test</Text>
                    <Text>Test</Text>
                </View>
            } />
            <Accordion onPress={useCallback(() => {
                collapse(2)
            },
                [isCollapsed, setIsCollapsed],
            )} title={"Bla bla"} isCollapsed={isCollapsed[2]} accordionRender={
                <View>
                    <Text>Test</Text>
                    <Text>Test</Text>
                </View>
            } />
            <Accordion onPress={useCallback(() => {
                collapse(3)
            },
                [isCollapsed, setIsCollapsed],
            )} title={"Bla bla"} isCollapsed={isCollapsed[3]} accordionRender={
                <View>
                    <Text>Test</Text>
                    <Text>Test</Text>
                </View>
            } />
            <Accordion onPress={useCallback(() => {
                collapse(4)
            },
                [isCollapsed, setIsCollapsed],
            )} title={"Lorem Ipsum"} isCollapsed={isCollapsed[4]} accordionRender={
                <View style={styles.accordionItemContainer}>
                     <View style={styles.accordionItemSpacing}>
                        <BodyText>Lorem Ipsum</BodyText>
                        <BodyText>Lorem Ipsum</BodyText>
                    </View>
                    <View>
                        <BodyText>Lorem ipsum</BodyText>
                        <BodyText>Lorem ipsum</BodyText>
                    </View>
                </View>
            } />

        </View>
    );
}

const styles = StyleSheet.create({
    container: {
        backgroundColor: '#000',
        flex: 1,
        borderTopWidth: 2,
        borderTopColor: '#fff'
    }
    ,
    iconContainer: {
        height: 50,
        width: 50,
        borderLeftWidth: 2,
        borderLeftColor: '#fff',
        overflow: 'hidden',
        justifyContent: 'center',
        alignItems: 'flex-end'
    },
    accordionContainerTitle: {
        color: '#000',
        fontSize: 16,
    },
    accordionItemContainer: {
        paddingVertical: 10,
    },
    accordionItemText: {
        color: '#000',
        fontSize: 16,
        marginHorizontal: 10,
    },
    accordionItemSpacing: {
        marginBottom: 10
    }
})

export default InformationScreen;
  1. Remove the hooks from. JSX/TSX
  2. Generate your accordion by function
  3. use the maps index prop to toggle accordion
import React, { useCallback, useLayoutEffect, useState } from 'react'
import { Text, View, StyleSheet, TouchableWithoutFeedback } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons'
import BodyText from '../components/Atomic/BodyText';
import Accordion from '../components/Accordion/Accordion';


const getaccordianItems = () => ([

                        <View style={styles.accordionItemSpacing}>
                            <BodyText>Lorem Ipsum</BodyText>
                            <BodyText>Lorem Ipsum</BodyText>
                        </View>
                        <View>
                            <BodyText>Lorem ipsum</BodyText>
                            <BodyText>Lorem ipsum</BodyText>
                        </View>,

<View>
                    <Text>Test</Text>
                    <Text>Test</Text>
                </View>,

<View>
                    <Text>Test</Text>
                    <Text>Test</Text>
                </View>,

<View>
                    <Text>Test</Text>
                    <Text>Test</Text>
                </View>,

<View>
                    <Text>Test</Text>
                    <Text>Test</Text>
                </View>

])

const InformationScreen = props => {

    
        const [isExpandedIndex, setIsExpanded] = useState();
    
        useLayoutEffect(() => {
            props.navigation.setOptions({
                title: 'Information',
                headerStyle: {
                    backgroundColor: '#000'
                },
                headerTitleStyle: {
                    fontSize: 22,
                    color: '#fff'
                },
                headerLeft: () => (
                    <View></View>
                ),
                headerRight: () => (
                    <TouchableWithoutFeedback onPress={() => {
                        props.navigation.popToTop()
                    }}>
                        <View style={styles.iconContainer}>
                            <Icon name="home" size={30} color={'#fff'} />
                        </View>
                    </TouchableWithoutFeedback>
                )
            })
        })
        return (
            <View style={styles.container}>
                {accordionList.map((item,index)=>(
                <Accordion onPress={()=> { setIsExpanded(index) }} title={"Lorem"} isCollapsed={isExpanded !== index} accordionRender={
                    <View style={styles.accordionItemContainer}>
                        {item}
                    </View>
                  } />
                )}
    
            </View>
        );
    }
    
    const styles = StyleSheet.create({
        container: {
            backgroundColor: '#000',
            flex: 1,
            borderTopWidth: 2,
            borderTopColor: '#fff'
        }
        ,
        iconContainer: {
            height: 50,
            width: 50,
            borderLeftWidth: 2,
            borderLeftColor: '#fff',
            overflow: 'hidden',
            justifyContent: 'center',
            alignItems: 'flex-end'
        },
        accordionContainerTitle: {
            color: '#000',
            fontSize: 16,
        },
        accordionItemContainer: {
            paddingVertical: 10,
        },
        accordionItemText: {
            color: '#000',
            fontSize: 16,
            marginHorizontal: 10,
        },
        accordionItemSpacing: {
            marginBottom: 10
        }
    })
    
    export default InformationScreen;

Okay after the help of Michael Bahl I was able to put some nicer code. His code was good but I did some adjustments to it. Now everything works good and I can put all I need in my accordion.

I had one small change in the accordionCompontent.js and on the screen, I applied the code that Michael showed with some small adjustments to make it more flexible for me.

//AccordionCompontent.js
import React from 'react'
import { StyleSheet, View, Text, Dimensions } from 'react-native';
import Collapsible from 'react-native-collapsible';
import { TouchableWithoutFeedback } from 'react-native-gesture-handler';

const Accordion = props => {


    return (
        <View>
            <TouchableWithoutFeedback onPress={props.onPress}>
            <View style={styles.accordionContainer}>
            <Text style={styles.accordionContainerTitle} >{props.title}</Text>
            </View>
            </TouchableWithoutFeedback>
            <Collapsible style={styles.accordionCollapsedContainer} 
             //The small change is at collapsed
             collapsed={!props.isCollapsed}>
                {props.accordionRender}
            </Collapsible>
        </View>
    );
}

export default Accordion;

const styles = StyleSheet.create({
    accordionContainer: {
        justifyContent:'center',
        borderBottomWidth: 1,
        borderBottomColor: '#fff',
       minHeight: Dimensions.get('window').height / 15
    },
    accordionCollapsedContainer: {
        borderBottomWidth: 1,
        borderBottomColor: '#fff'
    },
    accordionContainerTitle: {
        color: '#fff',
        fontSize: 16,
        marginLeft:10
    },

})
import React, { useCallback, useLayoutEffect, useState } from 'react'
import { Text, View, StyleSheet, TouchableWithoutFeedback } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons'
import BodyText from '../components/Atomic/BodyText';
import Accordion from '../components/Accordion/Accordion';

const getAccordionItems = (version, expiresDate, scriptSize, packageSize) => ([
    ['General Information',
        <View>
            <View style={styles.accordionItemSpacing}>
                <BodyText>Version {version}</BodyText>
                <BodyText>License expires on {expiresDate}</BodyText>
            </View>
            <View>
                <BodyText>Size of scripts folder: {scriptSize} MB</BodyText>
                <BodyText>Size of packages folder: {packageSize} MB</BodyText>
            </View>
        </View>],

    ['lorem',
        <View>
            <View style={styles.accordionItemSpacing}>
                <BodyText>Lorem Ipsum</BodyText>
                <BodyText>Lorem Ipsum</BodyText>
            </View>
            <View>
                <BodyText>Lorem ipsum</BodyText>
                <BodyText>Lorem ipsum</BodyText>
            </View>
        </View>],
    ['lorem',
        <View>
            <View style={styles.accordionItemSpacing}>
                <BodyText>Lorem Ipsum</BodyText>
                <BodyText>Lorem Ipsum</BodyText>
            </View>
            <View>
                <BodyText>Lorem ipsum</BodyText>
                <BodyText>Lorem ipsum</BodyText>
            </View>
        </View>],
])

const InformationScreen = props => {
    const accordionList = getAccordionItems('1.0', '2021/10/20', 1.00, 0.00)

    const [isExpanded, setIsExpanded] = useState();

    useLayoutEffect(() => {
        props.navigation.setOptions({
            title: 'Information',
            headerStyle: {
                backgroundColor: '#000'
            },
            headerTitleStyle: {
                fontSize: 22,
                color: '#fff'
            },
            headerLeft: () => (
                <View></View>
            ),
            headerRight: () => (
                <TouchableWithoutFeedback onPress={() => {
                    props.navigation.popToTop()
                }}>
                    <View style={styles.iconContainer}>
                        <Icon name="home" size={30} color={'#fff'} />
                    </View>
                </TouchableWithoutFeedback>
            )
        })
    })

    accordionList.map((item, index) => {
        console.log(item)
        console.log(index)
    })
    return (
        <View style={styles.container}>
            {accordionList.map((item, index) => (
                <Accordion onPress={() => { 
                    if (isExpanded === index){
                        setIsExpanded(accordionList.size)
                    } else{
                    setIsExpanded(index) 
                    }
            }} title={item[0]} isCollapsed={isExpanded === index} accordionRender={
                    <View style={styles.accordionItemContainer}>
                        {item[1]}
                    </View>
                } />
            ))}
        </View>
    );
}

const styles = StyleSheet.create({
    container: {
        backgroundColor: '#000',
        flex: 1,
        borderTopWidth: 2,
        borderTopColor: '#fff'
    }
    ,
    iconContainer: {
        height: 50,
        width: 50,
        borderLeftWidth: 2,
        borderLeftColor: '#fff',
        overflow: 'hidden',
        justifyContent: 'center',
        alignItems: 'flex-end'
    },
    accordionContainerTitle: {
        color: '#fff',
        fontSize: 16,
    },
    accordionItemContainer: {
        paddingVertical: 10,
    },
    accordionItemText: {
        color:'#fff',
        fontSize: 16,
        marginHorizontal: 10,
    },
    accordionItemSpacing: {
        marginBottom: 10
    }
})

export default InformationScreen;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM