react-nativereact-class-based-componentcreate-ref

How to use createRef to toggle Accordion list items


I have a FlatList and each item is an accordion, I m using class based react and I want to be able to toggle each accordion individually using createRef but I was unsuccessful

export default class shopingScreen extends React.component{
  constractor(props){
  super(props);
  this.state = {
  showAccordion : false
  }
  this.accordian = React.createRef();
  }
  
  handleListItem(item,index){
  return (
  <TouchableOpacity ref={this.accordian} onPress={()=>  {this.setState(prevState =>({!prevState.showAccordion})  )
     <Icon name='chevron-up'/>
  </TouchableOpacity>
  {this.state.showAccordion&&<Text>{item}</Text>
  }
  
  renderList(){
  return (
    <View>
      <FlatList
        data ={fakeList}
        keyExtractor ={(item,index)=> Math.random().toString()}
        renderItem={({item,index})=> this.handleListItem(item,index)}
    </View>
  )
  }
  }
  


Solution

  • Every thing gets much easier if you take handleListItem and make it its own component. Each item needs its own accordion, its own boolean state, its own ref, and its own Animation.Value (for the accordion effect). If you tried to manage all that logic in a single component it gets really messy (see AssetExample.js here)

    But when separated your list component from the list item component everything is much cleaner link

    // List component
    import React from 'react';
    import { View, FlatList, StyleSheet } from 'react-native';
    import { colorGenerator } from '@phantom-factotum/colorutils';
    import ListItem from './ListItem';
    
    const fakeList = colorGenerator(5).map((color, i) => ({
      color,
      title: 'Item ' + (i + 1),
      id: 'list-item-' + i,
    }));
    
    export default class ShoppingScreen extends React.Component {
      constructor(props) {
        super(props);
      }
      render() {
        return (
          <View>
            <FlatList
              data={fakeList}
              keyExtractor={(item, index) => item.id}
              renderItem={({ item, index }) => (
                <ListItem item={item} index={index} />
              )}
            />
          </View>
        );
      }
    }
    
    const styles = StyleSheet.create({});
    
    
    // list item
    import React from 'react';
    import { MaterialCommunityIcons } from '@expo/vector-icons';
    import {
      View,
      FlatList,
      TouchableOpacity,
      Text,
      Animated,
      StyleSheet,
    } from 'react-native';
    
    const ITEM_HEIGHT = 50;
    
    export default class ListItem extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          showAccordion: false,
        };
        this.itemHeight = new Animated.Value(0);
        this.itemRef = React.createRef(null);
      }
      render() {
        const showAccordion = this.state.showAccordion;
        const animatedStyle = {
          height: this.itemHeight.interpolate({
            inputRange: [0, 1],
            outputRange: [0, ITEM_HEIGHT],
          }),
          overflow: 'hidden',
        };
        return (
          <TouchableOpacity
            style={[
              styles.itemContainer,
              { backgroundColor: this.props.item.color },
            ]}
            ref={this.itemRef}
            onPress={() => {
              const nextVal = !showAccordion;
              Animated.timing(this.itemHeight, {
                toValue: nextVal ? 1 : 0,
                duration: 200,
              }).start();
              this.setState((prevState) => ({
                ...prevState,
                showAccordion: nextVal,
              }));
            }}>
            <MaterialCommunityIcons
              name={showAccordion ? 'chevron-up' : 'chevron-down'}
            />
            <Animated.View style={animatedStyle}>
              <Text>{this.props.item.title}</Text>
            </Animated.View>
          </TouchableOpacity>
        );
      }
    }
    
    const styles = StyleSheet.create({
      itemContainer: {
        padding: 5,
        paddingVertical: 10,
        marginVertical: 10,
        // overflow: 'hidden',
      },
    });