Home > Mobile >  How to expand card onPress - React Native
How to expand card onPress - React Native

Time:03-25

I am trying to make a ticket app that allows for people to create tickets based on work that needs done. Right now, I need help with the expandable view for each ticket card. What I'm wanting is when a user presses on a specific card, it will expand the view and provide more details for only that card. What it is currently doing is expanding the view for every ticket card in the list. I'm new to React Native and trying my best, but nothing has worked so far.

Here is my parent which is called Home:

import React, {useState, useEffect} from 'react';
import {styles, Colors} from '../components/styles';
import { SafeAreaView } from 'react-native';
import Ticket from '../components/Ticket';

const data = [
    {
        name: 'Josh Groban',
        subject: 'U-Joint',
        desc: 'The bolt that is meant to hold the u-joint in place has the head broken off from it. See attached picture.',
        assignee: 'John Doe',
        assigneeConfirmedComplete: 'NA',
        dateReported: 'Tue Mar 8, 2022',
        vehicle: 'Truck 1',
        media: '',
        key: '1',
        isShowing: false
      },
      // code removed for brevity
];

const Home = ({navigation}) => {

    const [ticketList, setTicketList] = useState(data);


    const getTickets = () => {
        setTicketList(data);
    }
    useEffect(() => {
        getTickets();
    }, []);

    return (
        <SafeAreaView  style={styles.HomeContainer}>
                <Ticket 
                    ticketList={ticketList}
                    setTicketList={setTicketList}
                />
        </SafeAreaView>
    )
};

export default Home;

And here is the main component that has all of the ticket card configurations:

import React, {useState, useEffect} from 'react';
import {Text, FlatList, View, SafeAreaView, Button, Image, TouchableOpacity } from 'react-native';
import {styles, Colors} from './styles';
import {Ionicons} from '@expo/vector-icons';

const Ticket = ({ticketList, setTicketList}) => {
    const defaultImage = 'https://airbnb-clone-prexel-images.s3.amazonaws.com/genericAvatar.png';
    const [isComplete, setIsComplete] = useState(false);
    const [show, setShow] = useState(false);

    const showContent = (data) => {
        const isShowing = {...data, isShowing}
        if (isShowing)
            setShow(!show);
    }

    const completeTask = () => {
        setIsComplete(!isComplete);
    }
    return (
        <SafeAreaView style={{flex: 1}}>
        <FlatList showsVerticalScrollIndicator={false} 
            data={ticketList}
            renderItem={(data) => {
                return (
            <>
            <TouchableOpacity key={data.item.key} onPress={() => showContent(data.item.isShowing = true)}>
                <View style={styles.TicketCard}>
                    <Image
                        style={styles.TicketCardImage}
                        source={{uri: defaultImage}} 
                    />
                    <View style={styles.TicketCardInner}>
                        <Text style={styles.TicketCardName}>{data.item.vehicle}</Text>
                        <Text style={styles.TicketCardSubject}>
                            {data.item.subject}
                        </Text>
                    </View>
                    <TouchableOpacity>
                        <Ionicons
                            name='ellipsis-horizontal-circle'
                            color={Colors.brand}
                            size={50}
                            style={styles.TicketCardImage}
                        />
                    </TouchableOpacity>
                    <TouchableOpacity onPress={completeTask}>
                        <Ionicons 
                            name={isComplete ? 'checkbox-outline' : 'square-outline'}
                            color={Colors.brand}
                            size={50}
                            style={styles.TicketCardButton}
                        />
                    </TouchableOpacity>
                </View>
                <View style={styles.TicketCardExpand}>
                    <Text>
                {show && 
                    (<View style={{padding: 10}}>
                        <Text style={styles.TicketCardDesc}>
                            {data.item.desc}
                        </Text>
                        <Text style={{padding: 5}}>
                            Reported by: {data.item.name}
                        </Text>

                        <Text style={{padding: 5}}>
                            Reported: {data.item.dateReported}
                        </Text>
                        {isComplete && ( 
                        <Text style={{padding: 5}}>
                            Confirmed Completion: {data.item.assigneeConfirmedComplete}
                        </Text>
                        )}
                    </View>
                    )}
                    </Text>
                </View> 
            </TouchableOpacity>
            </>
        )}}
        />
        </SafeAreaView>
    )
};

export default Ticket;

Lastly, here are the styles that i'm using:

import {StyleSheet } from "react-native";
import { backgroundColor } from "react-native/Libraries/Components/View/ReactNativeStyleAttributes";
// colors
export const Colors = {
    bg: '#eee',
    primary: '#fff',
    secondary: '#e5e7eb',
    tertiary: '#1f2937',
    darkLight: '#9ca3f9',
    brand: '#1d48f9',
    green: '#10b981',
    red: '#ff2222',
    black: '#000',
    dark: '#222',
    darkFont: '#bbb',
    gray: '#888'
}

export const styles = StyleSheet.create({
    HomeContainer: {
        flex: 1,
        paddingBottom: 0,
        backgroundColor: Colors.bg,
    },
    TicketCard : {
        padding: 10,
        justifyContent: 'space-between',
        borderColor: Colors.red,
        backgroundColor: Colors.primary,
        marginTop: 15,
        flexDirection: 'row',
    },
    TicketCardExpand: {
        justifyContent: 'space-between',
        backgroundColor: Colors.primary,
    },
    TicketCardImage: {
        width: 60,
        height: 60,
        borderRadius: 30
    },
    TicketCardName:{
        fontSize: 17,
        fontWeight: 'bold'
    },
    TicketCardSubject: {
        fontSize: 16,
        paddingBottom: 5
    },
    TicketCardDesc: {
        fontSize: 14,
        flexWrap: 'wrap',
    },
    TicketCardInner: {
        flexDirection: "column",
        width: 100
    },
    TicketCardButton: {
        height: 50,
    }

});

Any help is greatly appreciated!

CodePudding user response:

Create a Ticket component with its own useState.

const Ticket = (data) => {
  const [isOpen, setIsOpen] = useState(false);

  const handlePress = () => {
    setIsOpen(!isOpen);
  }

  return (
    <TouchableOpacity
      onPress={handlePress}
    >
      // data.item if you use a list, otherwise just data
      <YourBasicInformation data={data.item} />
      {isOpen && <YourDetailedInformation data={data.item} />}
    </TouchableOpacity>
  )
}

Render one Ticket for every dataset you have.

<List
  style={styles.list}
  data={yourDataArray}
  renderItem={Ticket}
/>

If you don't want to use a List, map will do the job.

{yourDataArray.map((data) => <Ticket data={data} />)}

CodePudding user response:

instead of setting show to true or false you can set it to something unique to each card like

setShow(card.key or card.id or smth)

and then you can conditionally render details based on that like

{show == card.key && <CardDetails>}

or you can make an array to keep track of open cards

setShow([...show,card.id])
{show.includes(card.id) && <CardDetails>}
//to remove
setShow(show.filter((id)=>id!=card.id))
  • Related