'React Native FlatList with custom buttons, how to change color on click?

I have created a FlatList that renders names. Each row has its own button. When you click the button, the name is added to a list and the button should change color

My problem is that the button does not change color as soon as I click on it, it only changes color when I make a pull refresh on my FlatList

Use my snack: https://snack.expo.io/@thesvarta/92f850

This is my component

  import React, { Component } from 'react';
  import { connect } from 'react-redux';
  import {
    View,
    Text,
    FlatList,
    StatusBar,
    StyleSheet,
    ActivityIndicator,
    TouchableOpacity,
    TouchableHighlight,
    Image,
    Button,
    SearchBar,
    TextInput,
  } from 'react-native';
  import TextDetails from '../TextDetails/TextDetails';
  import { selectOptions } from '../store/actions/index';
  class Search extends Component {
    constructor(props) {
      super(props);

      this.state = {
        loading: false,
        data: [],
        page: 1,
        error: null,
        refreshing: false,
        groupBy: '',
      };
    }
    setFetch = () => {
      this.setState(
        {
          groupBy: this.props.GroupBy,
        },
        () => {
          this.makeRemoteRequest();
        }
      );
    };
    componentDidMount() {
      this.setFetch();
    }

    makeRemoteRequest = () => {
      const { page, groupBy } = this.state;
      const url = `api/getOptions.php?page=${page}&row_per_page=5&group_by=${groupBy}`;
      fetch(url)
        .then(response => response.json())
        .then(responseJson => {
          this.setState(
            {
              data:
                page == 1
                  ? responseJson[0].DATA
                  : [...this.state.data, ...responseJson[0].DATA],
              status: responseJson[0].STATUS,
              message: responseJson[0].MESSAGE,
              isLoading: false,
              refreshing: false,
            },
            () => {}
          );
        });
    };
    handleRefresh = () => {
      this.setState(
        {
          page: 1,
          refreshing: true,
        },
        () => {
          this.makeRemoteRequest();
        }
      );
    };

    handleLoadMore = () => {
      this.setState(
        {
          page: this.state.page + 1,
        },
        () => {
          this.makeRemoteRequest();
        }
      );
    };

    renderSeparator = () => {
      return (
        <View
          style={{
            height: 1,
            width: '86%',
            backgroundColor: '#CED0CE',
            marginLeft: '14%',
          }}
        />
      );
    };
    inArray = (needle, haystack) => {
      let length = haystack.length;
      for (let i = 0; i < length; i++) {
        if (haystack[i] == needle) return true;
      }
      return false;
    };
    renderRow = ({ item, index }) => {
      return (
        <View style={styles.ListContainer}>
          <View style={styles.Text}>
            <TextDetails Size={18}>{item.name}</TextDetails>
          </View>
          <View style={styles.button}>
            <TouchableOpacity
              style={
                this.inArray(item.name, this.props.selectedOptions)
                  ? styles.buttonOrange
                  : styles.buttonGray
              }
              onPress={() => this.props.onSelectOptions(item.name)}
            />
          </View>
        </View>
      );
    };
    renderFooter = () => {
      if (!this.state.loading) return null;

      return (
        <View
          style={{
            paddingVertical: 20,
            borderTopWidth: 1,
            borderColor: '#CED0CE',
          }}>
          <ActivityIndicator animating size="large" />
        </View>
      );
    };
    render() {
      return (
        <View style={styles.SearchContatiner}>
          <View style={styles.Search}>
            <View style={styles.ImageIcon}>
              <Image
                style={styles.Image}
                resizeMode="contain"
                source={require('../images/sokbla.png')}
              />
            </View>
            <View style={styles.InputBox}>
              <TextInput
                style={styles.InputText}
                onChangeText={text => this.setState({ query: text })}
                placeholder={'Search for ' + this.props.Title}
                value={this.state.query}
              />
            </View>
            <TouchableOpacity
              onPress={() => alert(this.props.selectedOptions)}
              style={styles.KryssIcon}>
              <Image
                style={styles.ImageKryss}
                resizeMode="contain"
                source={require('../images/kryssbla.png')}
              />
            </TouchableOpacity>
          </View>
          <View style={styles.Lista}>
            <FlatList
              style={styles.Flatlist}
              data={this.state.data}
              renderItem={this.renderRow}
              keyExtractor={(item, index) => index.toString()}
              ItemSeparatorComponent={this.renderSeparator}
              ListFooterComponent={this.renderFooter}
              onRefresh={this.handleRefresh}
              refreshing={this.state.refreshing}
              onEndReached={this.handleLoadMore}
              onEndReachedThreshold={0.5}
            />
          </View>
        </View>
      );
    }
  }
  const styles = StyleSheet.create({
    button: {
      width: '20%',
      justifyContent: 'center',
      alignItems: 'center',
    },
    buttonGray: {
      width: 25,
      height: 25,
      borderRadius: 12.5,
      backgroundColor: 'gray',
    },
    buttonOrange: {
      width: 25,
      height: 25,
      borderRadius: 12.5,
      backgroundColor: 'orange',
    },
    Lista: {
      marginTop: 20,
    },
    Flatlist: {
      width: '100%',
      height: 300,
    },
    ListContainer: {
      flexDirection: 'row',
      width: '100%',
      height: 40,
    },
    Text: {
      marginLeft: '10%',
      width: '70%',
      justifyContent: 'center',
    },
    SearchContatiner: {},
    Search: {
      width: '100%',
      height: 60,
      backgroundColor: 'rgb(240,240,240)',
      flexDirection: 'row',
    },
    Image: {
      width: 23,
      height: 33,
    },
    ImageIcon: {
      justifyContent: 'center',
      width: '15%',
      alignItems: 'center',
    },
    InputBox: {
      width: '70%',
      justifyContent: 'center',
    },
    InputText: {
      paddingLeft: 20,
      width: '100%',
      height: 50,
      fontSize: 20,
    },
    KryssIcon: {
      justifyContent: 'center',
      width: '15%',
      alignItems: 'center',
    },
    ImageKryss: {
      width: 18,
      height: 28,
    },
  });
  const mapStateToProps = state => {
    return {
      selectedOptions: state.filter.selectedOptions,
    };
  };
  const mapDispatchToProps = dispatch => {
    return {
      onSelectOptions: name => dispatch(selectOptions(name)),
    };
  };
  export default connect(
    mapStateToProps,
    mapDispatchToProps
  )(Search);

This is my Action

    import { OPTIONS_SELECT } from './actionTypes';

    export const selectOptions = name => {
      return {
        type: OPTIONS_SELECT,
        selectedOptions: name,
      };
    };

And my reducer

    import {
      OPTIONS_SELECT,
    } from '../actions/actionTypes';

    const initialState = {
      selectedOptions:[],
    };

    const reducer = (state = initialState, action) => {
      switch (action.type) {
        case OPTIONS_SELECT:
          return {
            ...state,
            selectedOptions: [...state.selectedOptions, action.selectedOptions],
          };
        default:
          return state;
      }
    };
    export default reducer;


Solution 1:[1]

The extraData property of the FlatList component is missing:

By passing extraData={this.state} to FlatList we make sure FlatList itself will re-render when the state.selected changes. Without setting this prop, FlatList would not know it needs to re-render any items because it is also a PureComponent and the prop comparison will not show any changes.

LInk to the docs: https://facebook.github.io/react-native/docs/flatlist

This property updates the FlatList when the passed object is updated. In your case you need to pass:

extraData={this.props.selectedOptions}

Here is a working demo of your snack.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Hristo Eftimov