Apswak Apswak - 1 month ago 13
React JSX Question

Target a dynamically rendered ListView component with refs

Teaching myself react native by making a chat app, now when someone clicks on a delete button next to the message (not visible in this code as it's irrelevant), it deletes it from the database but I need to make the changes here in the app.

For this I have set a ref one each of the

<Message/>
components and I have the ref (which I named snappy) which matches the ref on the component. But i need to target the actual component node which has that ref.

Is refs the way to go about doing this? If not, what else can I do?

Many thanks

edit: Full code:

import React, { Component } from "react"
import { ListView, View, Text, StyleSheet, TextInput, TouchableHighlight } from "react-native"
import * as firebase from 'firebase';
import Icon from 'react-native-vector-icons/FontAwesome'
import { Hideo } from 'react-native-textinput-effects';

import Message from './Message'

export default class Chat extends Component {
constructor() {
super();
this.ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.onSend = this.onSend.bind(this);
this.state = {
messages: this.ds.cloneWithRows([]),
messageContent: '',
};
}
componentWillMount() {
// Get all of the messages from firebase database and push them into "nodes" array which then gets set in the "messages" state above in handleChat.
const chatRef = firebase.database().ref().child('general');
this.messages = [];
chatRef.on('child_added', snap => {
this.messages.push({user: snap.val().user.name,
text: snap.val().text,
messageId: snap.key
})
this.handleChat(this.messages);
}
).bind(this);
// this is what happens when someone removes a comment
chatRef.on('child_removed', snap => {
const messageId = snap.key; // <- This is the key in the database, for example: '-KVZ_zdbJ0HMNz6lEff'
this.removeMessage(messageId);
})
}
removeMessage(messageId){
let messages = this.messages.filter(message => message.messageId !== messageId);
this.handleChat(messages);
}
handleChat(messages) {
this.setState({messages: this.ds.cloneWithRows(messages)})
}
onSend(messages) {
const generalRef = firebase.database().ref().child('general');
const user = firebase.auth().currentUser;
generalRef.push(
{
_id: 1,
text: this.state.messageContent,
createdAt: new Date().getTime(),
user: {
_id: 2,
name: user.displayName,
avatar: 'http://mdepinet.org/wp-content/uploads/person-placeholder.jpg'
}
});
this.setState({messageContent: ''})
}
removeMessage(messageId){
let messages = this.messages.filter(message => message.messageId !== messageId);
this.handleChat(messages);
}
render() {
return (
<View style={{flex: 1, alignItems: 'flex-end'}}>
<ListView
style={{ marginBottom: 60 }}
enableEmptySections={true}
dataSource={this.state.messages}
renderRow={message => <Message name={message.user} text={message.text}/> }/>

<Hideo
style={{position: 'absolute', bottom: 0}}
onChangeText={messageContent => this.setState({messageContent})} value={this.state.messageContent} placeholder="Name"
iconClass={Icon}
iconName={'envelope'}
iconColor={'white'}
iconBackgroundColor={'#222'}
inputStyle={{ color: '#464949' }}
/>
<TouchableHighlight onPress={this.onSend} style={{position: 'absolute', alignItems: 'center', bottom: 10, right: 10, borderRadius: 10, backgroundColor: '#d4af37'}}>
<Text style={{color: 'whitesmoke', fontSize: 20, padding: 5}}>Send</Text>
</TouchableHighlight>
</View>
);
}
}

const styles = StyleSheet.create({
username: {
fontFamily: 'AvenirNext-Bold'
},
comment: {
fontFamily: 'AvenirNext-Regular'
},
bubble: {
flex: 1,
width: 250,
backgroundColor: '#f5f5f5',
margin: 15,
padding: 10,
borderRadius: 20
}
})

Answer

Using refs to ListView row items is not a Good idea. As Elmeister told we just need to remove the message elements from array and update the ListView datasource in order to delete a message from ListView.

Here is a sample code which will give you an idea of how you can do the same in your app.

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


class StackOverflow extends Component {

  constructor(props) {
    super(props);
    const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
    this.messages = this.getMessages();
    this.state = {
      dataSource: ds.cloneWithRows(this.messages)
    };
  }


  getMessages() {
    let arr = [];
    for (let i = 0; i < 100; i++) {
      arr.push({
        user: 'User ' + i,
        text: 'This is a sample user message ' + i,
        messageId: 'messageId' + i,
      });
    }
    return arr;
  }

  onDeletePress(messageId) {
    this.messages = this.messages.filter(message => message.messageId !== messageId);
    this.setState({
      dataSource: this.state.dataSource.cloneWithRows(this.messages)
    });
  }

  renderRow(rowData) {
    return (
      <View style={{padding:8}}>
        <Text>{rowData.user}</Text>
        <Text>{rowData.text}</Text>
        <TouchableHighlight
          style={{alignSelf :'flex-end',backgroundColor:'red'}}
          onPress={this.onDeletePress.bind(this,rowData.messageId)}>
          <Text>Delete</Text>
        </TouchableHighlight>
      </View>
    );
  }

  render() {
    return (
      <View style={{flex: 1, paddingTop: 22}}>
        <ListView
          dataSource={this.state.dataSource}
          renderRow={this.renderRow.bind(this)}
        />
      </View>
    );
  }
}

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

In the above example, We are creating dummy messages and saving them in messages array and updating dataSource with messages.

When onDeletePress is called we pass the messageId to that method, and below line

this.messages = this.messages.filter(message => message.messageId !== messageId); removes the message from messages array. Then we update the dataSource state which will update the ListView.

In your code probably these changes you will have to make,

Change handleChat

handleChat(messages) {
    this.setState({messages: this.ds.cloneWithRows(messages)})
}

Update Chat component like below,

import React, {Component} from "react"
import {ListView, View, Text, StyleSheet, TextInput, TouchableHighlight} from "react-native"
import * as firebase from 'firebase';
import Icon from 'react-native-vector-icons/FontAwesome'
import {Hideo} from 'react-native-textinput-effects';

import Message from './Message'

export default class Chat extends Component {
  constructor() {
    super();
    this.chatRef = firebase.database().ref().child('general');
    this.ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
    this.onSend = this.onSend.bind(this);
    this.state = {
      messages: this.ds.cloneWithRows([]),
      messageContent: '',
    };
  }

  componentWillMount() {
    // Get all of the messages from firebase database and push them into "nodes" array which then gets set in the "messages" state above in updateMessageList.
    this._messages = [];
    this.chatRef.on('child_added', snap => {
        this._messages.push({
          user: snap.val().user.name,
          text: snap.val().text,
          messageId: snap.key
        });
        this.updateMessageList(this._messages);
      }
    ).bind(this);
    // this is what happens when someone removes a comment
    this.chatRef.on('child_removed', snap => {
      const messageId = snap.key; // <- This is the key in the database, for example: '-KVZ_zdbJ0HMNz6lEff'
      this.removeMessage(messageId);
    }).bind(this);
  }

  removeMessage(messageId) {
    this._messages = this._messages.filter(message => message.messageId !== messageId);
    this.updateMessageList(this._messages);
  }

  updateMessageList(messages) {
    this.setState({messages: this.ds.cloneWithRows(messages)})
  }

  onSend(messages) {
    const user = firebase.auth().currentUser;
    this.chatRef.push(
      {
        _id: 1,
        text: this.state.messageContent,
        createdAt: new Date().getTime(),
        user: {
          _id: 2,
          name: user.displayName,
          avatar: 'http://mdepinet.org/wp-content/uploads/person-placeholder.jpg'
        }
      });
    this.setState({messageContent: ''})
  }

  render() {
    return (
      <View style={{flex: 1, alignItems: 'flex-end'}}>
        <ListView
          style={{ marginBottom: 60 }}
          enableEmptySections={true}
          dataSource={this.state.messages}
          renderRow={message => <Message name={message.user} text={message.text}/> }/>

        <Hideo
          style={{position: 'absolute', bottom: 0}}
          onChangeText={messageContent => this.setState({messageContent})} value={this.state.messageContent}
          placeholder="Name"
          iconClass={Icon}
          iconName={'envelope'}
          iconColor={'white'}
          iconBackgroundColor={'#222'}
          inputStyle={{ color: '#464949' }}
        />
        <TouchableHighlight onPress={this.onSend}
                            style={{position: 'absolute', alignItems: 'center',   bottom: 10, right: 10, borderRadius: 10, backgroundColor: '#d4af37'}}>
          <Text style={{color: 'whitesmoke', fontSize: 20, padding: 5}}>Send</Text>
        </TouchableHighlight>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  username: {
    fontFamily: 'AvenirNext-Bold'
  },
  comment: {
    fontFamily: 'AvenirNext-Regular'
  },
  bubble: {
    flex: 1,
    width: 250,
    backgroundColor: '#f5f5f5',
    margin: 15,
    padding: 10,
    borderRadius: 20
  }
});