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;
Advertisement
Answer
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.