whosbuddy
whosbuddy

Reputation: 65

How to setState of a particular index in an array React Native

I have an array of objects that I am currently mapping over to generate as buttons. When clicked, I want the background color of the specific button the user clicks on to change color ( I want it to toggle on, like a switch, so I can eventually save to async storage). Right now when the user clicks, all buttons change color. I'm not quite sure how I should handle this.setState in the selectMetric function.

import React, {Component} from 'react';
import {View, Text, ScrollView} from 'react-native';
import {Button} from 'react-native-elements';

const RISK_DATA = [
  {id: 1, text: 'cats', flag: false, buttonColor: null},
  {id: 2, text: 'dogs', flag: false, buttonColor: null},

]


class IssueSelectionScreen extends Component {

  state = {flag: false, buttonColor: null}

  selectMetric = (index) => {

    for (let i=0; i < RISK_DATA.length; i++) {

      if (index === (RISK_DATA[i].id - 1)) {

      console.log("RISK_DATA:", RISK_DATA[i]); // logs matching id

// ------------------------------------------------------
// Problem setting correct state here:

      RISK_DATA[i].buttonColor = this.setState({flag: true, buttonColor: '#03A9F4'})

      // this.setState({flag: true, buttonColor: '#03A9F4'})
      // this.setState({update(this.state.buttonColor[i], {buttonColor: {$set: '#03A9F4'}}) })


// ----------------------------------------------------------
      }
    }
  }

  showMetric() {
    return RISK_DATA.map((metric, index) => {
      return (
        <View key={metric.id}>
          <Button
            raised
            color={'black'}
            title={metric.text}
            borderRadius={12}
            onPress={() => this.selectMetric(index)}
            backgroundColor={this.state.buttonColor}
          >
            {metric.text}
          </Button>
          <Text>{/* intentionally blank*/} </Text>
        </View>
      )
    })
  }

  render() {
    return(
      <ScrollView style={styles.wrapper}>
        <View style={styles.issues}>
          {this.showMetric()}
        </View>
      </ScrollView>
    );
  }
}


const styles = {
  issues: {
    justifyContent: 'center',
    flexDirection: 'row',
    flexWrap: 'wrap',
    alignItems: 'flex-start',
    marginTop: 10,
    justifyContent: 'space-between',
  },
  wrapper: {
    backgroundColor: '#009688'
  }
}

export default IssueSelectionScreen;

Upvotes: 1

Views: 5502

Answers (2)

whosbuddy
whosbuddy

Reputation: 65

For anyone else viewing this post, the answer above is very helpful. To add a few last remarks, if you're trying to get the buttons to light up I added a simple if else to selectMetric:

if (tempData[index].flag) {
  tempData[index].buttonColor = '#03A9F4';
  console.log('tempData true:', tempData);
} else {
  tempData[index].buttonColor = null;
  console.log('tempData false:', tempData);
}

and updated the backgroundColor property on Button in showMetric with:

backgroundColor={this.state.data[index].buttonColor}

Upvotes: 0

Garrett McCullough
Garrett McCullough

Reputation: 980

so the short answer to your question would look something like this:

class IssueSelectionScreen extends Component {
  constructor(props) {
    super(props);
    this.state = {
      data: cloneDeep(RISK_DATA),
    };
  }

  selectMetric = (index) => {
    const tempData = cloneDeep(this.state.data);
    tempData[index].flag = !tempData[index].flag;
    this.setState({ data: tempData });
  }

  showMetric() {
    return this.state.data.map((metric, index) => {
      // same
    })
  }

  render() {
    // same
  }
}

It involves putting the whole array of buttons into state since the state of those buttons is what can change. You could also maintain the flags as an array in state and keep the button info as a separate constant

This solution uses cloneDeep (from lodash) to prevent the code from mutating the state of the objects but you could probably also do it with this.state.data.map and creating new objects (which works as long as your objects aren't deeply nested).

If you're using Redux, the list would probably come into the component as a prop, then selectMetric would be dispatching an action to update the flag in Redux.

Upvotes: 2

Related Questions