1

I was following the React Native tutorial and have tried to adapt it to show a list of songs rather than movies and add in a toggling ability using the Switch component.

I managed to get this to work but now I am trying to send the value of the switch back to the parent so that a conditional style can be applied.

When I attempted to do this, I get an error saying

undefined is not an object (evaluating 'this.state.played')

which seems sensible since the console statement in the togglePlayed never seems to be called.

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

var SONGS_DATA = {
  "songs" : [
    {
      "title" : "I Heard React Was Good",
      "artist" : "Martin",
      "played" : false
    },
    {
      "title" : "Stack Overflow",
      "artist" : "Martin",
      "played" : false
    }
  ]
}

var BasicSwitchExample = React.createClass({
  getInitialState() {
    return {
      played: false
    };
  },
  handlePlayed(value) {
    console.log('Switch has been toggled, new value is : ' + value)
    this.setState({played: value})
    this.props.callbackParent(value);
  },
  render() {
    return (
      <View> 
        <Switch
          onValueChange={this.handlePlayed}
          style={{marginBottom: 10}}
          value={this.state.played} />
      </View>
    );
  }
});

class AwesomeProject extends Component {
  constructor(props) {
    super(props);
    this.state = {
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
      loaded: false,
    };
  }

  componentDidMount() {
    this.fetchData();
  }

  getInitialState() {
    return {
      played: false
    };
  }

  togglePlayed(value) {
    // this is never reached
    this.setState({played: value});
    console.log('Song has been played? ' + this.state.played);
  }

  fetchData() {
    this.setState({
      dataSource: this.state.dataSource.cloneWithRows(SONGS_DATA.songs),
      loaded: true,
    });
  }

  render() {
    if (!this.state.loaded) {
      return this.renderLoadingView();
    }

    return (
      <ListView
        dataSource={this.state.dataSource}
        renderRow={this.renderSong}
        style={styles.listView}
      />
    );
  }

  renderLoadingView() {
    return (
      <View style={styles.container}>
        <Text>
          Loading songs...
        </Text>
      </View>
    );
  }

  renderSong(song) {
    return (
      // not sure if this syntax is correct
      <View style={this.state.played ? 'styles.container' : 'styles.played'}>
        <View style={styles.half}>
          <Text style={styles.title}>{song.title}</Text>
          <Text style={styles.artist}>{song.artist}</Text> 
        </View>
        <View style={styles.half}>
          <BasicSwitchExample callbackParent={() => this.togglePlayed} />
        </View>
      </View>
    );
  }
}

var styles = StyleSheet.create({
  /* styles here */
});

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

React Native Playground

Any pointers would be great as I am new to React and especially React Native.

4
  • you should remove the styles and the songs in order to make it easier to read :P Commented Apr 24, 2016 at 19:53
  • yeah, obviously those songs aren't the real data, I truncated it to two made up examples to try and get some brevity. I can remove them, I just thought it would be better to include them. Commented Apr 24, 2016 at 19:55
  • I guess that your problem is that you have not binded togglePlayed to your component, you forgot to bind it in the constructor, at least thats how it works in React(I don't know about React native tho!) Commented Apr 24, 2016 at 19:56
  • @QoP I have noticed the use of bind when reading other questions about passing state from child to parent components but one example I seen didn't use it so I'm a bit confused when it's required and when it's not. If you have a solution I'd gladly mark it accepted if you have time to post it? Commented Apr 24, 2016 at 19:58

1 Answer 1

1

You forgot to bind your function to your component, it should look like this

class BasicSwitchExample extends Component{
    constructor(props){
      super(props);
      this.state = {
         played: false
       };
      this.handlePlayed = this.handlePlayed.bind(this);
    }
    handlePlayed(value){
        this.setState({played: value});
        this.props.callbackParent(value);
    }
    render() {
      return <View> 
        <Switch
          onValueChange={this.handlePlayed}
          style={{marginBottom: 10}}
          value={this.state.played} />
      </View>
  }
}
class AwesomeProject extends Component {
  constructor(props) {
    super(props);
    this.renderSong = this.renderSong.bind(this);
    this.togglePlayed = this.togglePlayed.bind(this); 
    this.fetchData = this.fetchData.bind(this);
    this.state = {
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
      loaded: false,
    };
  }

  componentDidMount() {
    this.fetchData();
  }

  togglePlayed(value) {
    // this is never reached
    this.setState({played: value});
    console.log('Song has been played? ' + this.state.played);
  }

  fetchData() {
    this.setState({
      dataSource: this.state.dataSource.cloneWithRows(SONGS_DATA.songs),
      loaded: true,
    });
  }

  render() {
    if (!this.state.loaded) {
      return this.renderLoadingView();
    }

    return (
      <ListView
        dataSource={this.state.dataSource}
        renderRow={this.renderSong}
        style={styles.listView}
      />
    );
  }

  renderLoadingView() {
    return (
      <View style={styles.container}>
        <Text>
          Loading songs...
        </Text>
      </View>
    );
  }

  renderSong(song) {
    return (
      // not sure if this syntax is correct
      <View style={this.state.played ? 'styles.container' : 'styles.played'}>
        <View style={styles.half}>
          <Text style={styles.title}>{song.title}</Text>
          <Text style={styles.artist}>{song.artist}</Text> 
        </View>
        <View style={styles.half}>
          <BasicSwitchExample callbackParent={this.togglePlayed} />
        </View>
      </View>
    );
  }
}
Sign up to request clarification or add additional context in comments.

10 Comments

thank you very much @QoP, seems to be the same error though (rnplay.org/apps/mnanvw). If the conditional style is removed, then the console logging from togglePlayed still doesn't seem to be reached, do you have any ideas?.
The comment I wrote made no sense, I didn't see that you were using ES5 syntax, anyway try to write them both in ES6 syntax
hmm, being new to this I am unsure, that's why I put getInitialState in the switch component so that played was initialised to false. Are you able to update the answer with your suggestion? I'll give it a try in the meantime.
I added the BasicSwitch component
thank you very much. The error I get now is 'this' is not allowed before super(). Just taking a look just now to see if I can figure out what's wrong.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.