0

I'm trying to build a simple app that takes a json containing a list of names with their corresponding 1 or 0 "active" state and displays them on a listview representing the "active" state using a switch.

I went through the tutorial and came up with this:

import React, { Component } from 'react';
import {
  AppRegistry,
  Switch,
  Text,
  View,
  ListView
} from 'react-native';

import data from './test-users.json';
// this contains:
// name: a string containing the name
// active: the state of the switch, either 1 or 0.

const Row = (props) => (
  <View>
    <Text>
      {`${props.name}`}
    </Text>
    <Switch value={props.active == 1}
            onValueChange={(value) => this.setState(value)} />
  </View>
);

class ListViewTest extends React.Component {
  constructor(props) {
    super(props);
    const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
    this.state = {
      dataSource: ds.cloneWithRows(data),
    };
  }
  render() {
    return (
      <ListView
        dataSource={this.state.dataSource}
        renderRow={(data) => <Row {...data} />}
      />
    );
  }
}

export default ListViewTest;

AppRegistry.registerComponent('ListViewTest', () => ListViewTest);

This displays fine, but when I try to click on a switch I get the following error stating:

undefined is not a function (evaluating '_this.setState(value)')

I feel like I must be misunderstanding something rather fundamental about how react-native works, but I can't quite figure out what it is. Is setState not the way to change state?

user1090729
  • 1,523
  • 3
  • 13
  • 17

1 Answers1

0

What I think you're looking for is something like this:

Firstly, make your Row function a true component, instead of a quick 'arrow function' approach. This is necessary so each individual row can have its own state.

class Row extends React.Component

Then, make a couple simple changes:

onValueChange={this.updateActive.bind(this)}

Here, this is referencing an instance of Row And then define the function on Row that updates your state:

updateActive = (value) => { this.setState({ active : value }) }

This will take the new value of the switch, and set the active state accordingly.

jaws
  • 1,952
  • 4
  • 20
  • 27
  • Thank you. What you are saying makes sense, but after trying it, I get a similar error again: undefined is not a function (evaluating '_this.setState({ active: value })'). It makes me think that I've got something else wrong. Perhaps I can't call setState from there? – user1090729 Apr 30 '17 at 20:44
  • You're right, sorry about that. I've updated the answer to include a change that gets the "this context" correct. What I had before was wrong because inside of the event handler the value for this was not your Row object. Now, by binding the update function to Row, it will be. Give that a try! – jaws Apr 30 '17 at 21:19
  • I was sure that would fix it, but the error is still the same. I've edited the question and added my current code (with your suggestions) to the bottom, in case I've made some syntax mistake. I'm having a hard time wrapping my head around how context binding works. – user1090729 Apr 30 '17 at 22:14
  • I just read your code again, and realized that the Row object isn't a Component, and therefore doesn't have a setState function. If you were to do so, that should clear things up. – jaws Apr 30 '17 at 22:19
  • Thank you! I made Row a subclass of View and it works perfectly, I was trying to take a very stupid shortcut with that arrow function. Would you mind updating your answer so that I can mark it as accepted? – user1090729 Apr 30 '17 at 22:56
  • 1
    It's extremely poorly documented, but you should never use `bind` or an arrow function like either `onValueChange={(value) => this.setState(value)}` or `onValueChange={this.updateActive.bind(this)}` in the `render` method. Especially not for components which are children of a list. Because these both create a new function every time and that causes a change of state, which in turn will cause all of the child nodes to re-render each time on switch is changed! I'll link to some of the right answers in a minute when I find one ... – hippietrail Sep 25 '17 at 02:08
  • [Here's one good article](https://medium.freecodecamp.org/why-arrow-functions-and-bind-in-reacts-render-are-problematic-f1c08b060e36) and a good [SO question on the same topic](https://stackoverflow.com/questions/36677733). – hippietrail Sep 25 '17 at 02:14
  • 1
    Thanks @hippietrail good point! The bind should happen in the constructor – jaws Sep 27 '17 at 19:02