0

I'm very new to React Native (and React/Javascript in general to be honest) and am very stuck. I have a chore tracking app that renders a list of chores using Flatlist, that can then be swiped/checked off using React Native Gesture Handler Swipeable.

I want a tag above the chore list that shows the total number of chores completed (the placeholder currently says "?? Chores complete"). I know this entails finding 1) How many of my ChoreListItems are rendered from the Flatlist component and then 2) How many of those items have a state of "isComplete". I have several nested/reusable components since there will be several versions of this screen (Laundry, kitchen, bathroom, etc.) and I know that's making it even more confusing for me. I feel like there's probably an obvious answer here, but I'm lost on how to even begin unpacking this.

Here is the ChoreListItem (what is rendered from Flatlist and able to be swiped):

import React, { useState, useRef} from 'react';
import { Animated, Image, StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native';
import Swipeable from 'react-native-gesture-handler/Swipeable';
import appStyles from '../config/appStyles';
import colors from '../config/colors';
import BodyText from '../config/BodyText';

function ChoreListItem({title}) { 
    const [isComplete, setIsComplete] = useState(false);
    const swipeableRef = useRef();
    //Track interaction occurs on left swipe
    const LeftActions = (progress, dragX) => {
        const scale = dragX.interpolate({
            inputRange: [0, 100],
            outputRange: [0,1],
            extrapolate: 'clamp',
        });
        if (isComplete == false) {
            return (
                <View style ={[appStyles.card, styles.leftActions]}>
                    <Animated.Text style={[styles.swipeText, {transform: [{scale}], fontSize:16, }]}>Swipe to track</Animated.Text>
                    <Image source={require('../assets/control_arrow_right.png')} style={{alignItems:'center',tintColor:colors.baseWhite,}}/>
                </View>
            );
        }
    };
    //Untrack button renders on right swipe
    const RightActions = (progress, dragX) => {
        const scale = dragX.interpolate({
            inputRange: [-100,0],
            outputRange: [1,0],
            extrapolate: 'clamp',
        });
        if (isComplete === true) {
            return (
                <TouchableWithoutFeedback onPress={closeSwipeable}>
                <View style ={[appStyles.card, styles.rightActions]}>
                    <Animated.Text style={[styles.swipeText,{transform: [{scale}], fontSize:16, }]}>Tap to untrack</Animated.Text>
                </View>
                </TouchableWithoutFeedback>
            );
        }
    };
    //Closes swiped action and changes state
    const closeSwipeable = () => {
        if (isComplete===false) {
            setIsComplete (true);
            console.log(title + ' tracked');
        } else {
            setIsComplete(false);
            console.log(title + ' untracked');
        }
    }
    return (
        <Swipeable 
        ref={swipeableRef}
        state={isComplete}
        renderLeftActions={LeftActions} 
        leftThreshold={20}
        rightThreshold={10}
        overshootRight={false}
        renderRightActions={RightActions}
        onSwipeableLeftOpen={closeSwipeable}
        >
            <View style={[appStyles.card, styles.choreListItem]}>
                <BodyText style={{textDecorationLine: isComplete ? "line-through" : "none"}}>{title}</BodyText>
                <Image style={[styles.checkmark, {display: isComplete ? "flex" : "none"}]} source={require('../assets/checkmark_large.png')}/>
            </View> 
        </Swipeable>
    );
}
export default ChoreListItem;
const styles = StyleSheet.create({
    checkmark: {
        width:16,
        height:16,
    },
    choreListItem: {
        paddingLeft:16,
        paddingRight:16,
        paddingTop:20,
        paddingBottom:20, 
        marginBottom:16,
        flex:1,
        flexDirection:'row',
        alignItems:'center',
        justifyContent:'space-between'
    },
    swipeText: {
        color: colors.baseWhite,
    },
    leftActions: {
        paddingLeft:16,
        paddingRight:16,
        paddingTop:20,
        paddingBottom:20,
        marginBottom: 16,
        backgroundColor: colors.primaryBlue,
        flex: 1,
        shadowColor: 'transparent',
        alignItems:'center',
        flexDirection:'row'
    },
    rightActions: {
        paddingLeft:16,
        paddingRight:16,
        paddingTop:20,
        paddingBottom:20,
        marginBottom: 16,
        backgroundColor: colors.primaryPurple,
        shadowColor: 'transparent',
        alignItems:'flex-end',
        flexDirection:'row',
        
    },
});

Here is the ChoreList (includes the Flatlist component):

import React from 'react';
import {FlatList, StyleSheet, Text, View} from 'react-native';
import appStyles from '../config/appStyles';
import colors from '../config/colors';
import ChoreListItem from '../components/ChoreListItem';
import SectionTitleBar from '../components/SectionTitleBar';

function ChoreList({getCategory}) {
    return (
        <View style={appStyles.containerPadding}>
            {/* <SectionTitleBar title="Today"/> */}
            <View>
                <FlatList
                    data = {getCategory}
                    renderItem={({item}) =>
                        <ChoreListItem title={item.title} />
                    }  
                    keyExtractor={(item) => item.title.toString()}
                />
            </View>
        </View>
    );
}
export default ChoreList;
const styles = StyleSheet.create({
    choreListItem: {
        padding:16,
        marginBottom:16,
    },
});

Here is the component/screen with all of the props:

import React from 'react';
import { Text, View } from 'react-native';
import choreCats from '../config/choreCats';
import choreItems from '../config/choreItems';
import colors from '../config/colors';
import ChoreList from '../components/ChoreList';
import PageHeader_TitleIllo from '../components/pageHeaders/PageHeader_TitleIllo';
import Screen from '../components/Screen';

function LaundryChoresScreen() {
    const choreCategory = choreCats[4];
    return (
        <Screen>
            <PageHeader_TitleIllo 
                category={choreCategory.category}
                image={choreCategory.image}
                bgColor={choreCategory.bgColor}
            />
            <Text style={{padding:8, backgroundColor:colors.blueHueMedium, alignSelf:'flex-start', marginTop: 8, marginBottom: 8}}>?? Chores complete</Text>
            <View style={{backgroundColor:colors.baseFog,}}>
                <ChoreList getCategory={choreItems.filter(({category})=>category=== "laundry")}/>
            </View>
        </Screen>
    );
}

export default LaundryChoresScreen;
kcinnamon
  • 1
  • 1

1 Answers1

1

There are a couple of different ways to accomplish this, but probably the best is for you to lift the state of your completed chores up to the level where it's shared with this new functionality. You would do that by tracking the complete/incomplete quality in the highest place where it needs to be shared. Currently, that's your LaundryChoresScreen component.

You would then need to pass the function that changes your array of chores down to the props of the ChoreListItem to invoke whenever they need to change their chore's status. This process is called prop drilling. A lot of people are frustrated with the tedium of prop drilling, and will opt to create a global state to manage things from anywhere in the application (using Contexts or Redux), but those are probably overkill for this case.

samuei
  • 1,949
  • 1
  • 10
  • 26
  • Ahh okay, yes that is helpful and I lifted the state. This has caused a different problem now. I left the other functions (leftActions, closeSwipeable, etc) as part of ChoreListItem component, and now the swiping gestures that set the state aren't working correctly. When I swipe 1 item, it is changing the state of all of the items. And in general, the state changes aren't working correctly This only started happening when I lifted the state - any idea what the relation is? Here's a video to show the issue: https://drive.google.com/file/d/1u6x2m9Z2BZimcdb7C8n5UFiG1k8crGGC/view?usp=sharing – kcinnamon Mar 01 '21 at 23:03
  • Without seeing your code, I can't be sure. I'd guess your state is still set up for tracking the status of one item, instead of tracking each one individually. Do the chores have ids? That would make it easier to keep track of which item you're swiping on. – samuei Mar 02 '21 at 06:08
  • The chores are setup like so: const choreItems = [ { title: "Load dishes", category: "kitchen", }, { title: "Unload dishes", category: "kitchen", }, { title: "Hand wash dishes", category: "kitchen", } ] – kcinnamon Mar 03 '21 at 23:49