Creating a React Native App With a Ruby On Rails Backend (Part 3 of 3)

Written by Armaiz Adenwala

In the previous article, we set up React Native and Redux. In the final installment of this series, we will complete the app by building out the UI and connecting it to Redux


Note: Don’t forget to keep the Ruby on Rails API running in a separate terminal tab so you can test your connection throughout the tutorial.


Container and Presentational Components


Most React apps that use Redux follow a similar pattern of separating components that interact with Redux and components that manage the UI. You can read more about this distinction in Dan Abramov’s article Presentational and Container Components. To follow this pattern, we will create two files for the home screen homeContainer and home in separate folders. The presentational file, home.js, will handle note display and creation, while homeContainer.js, will connect home.js to the reducer and actions. Create these files below:


    
$ mkdir -p app/containers/ app/screens/
$ touch app/containers/homeContainer.js app/screens/home.js
    
  

This results in the file structure shown below:


    
app/
  containers/
      homeContainer.js
  screens/
      home.js
    
  

Creating the Container Component


Redux provides us with a method called connect which takes two functions: mapStateToProps and mapDispatchToProps. These functions provide the connect method with our home screen’s reducers and actions.


We can leverage the mapStateToProps function to pass notes from the Redux state to our presentational component, home.


    
import {bindActionCreators} from 'redux';
import * as notesActions from '../actions/notesActions';

const mapStateToProps = state => ({
  notes: state.notes,
});        
    
  

The mapDispatchToProps function works similarly to the mapStateToProps by passing actions to our presentational component. To provide each action with the dispatch method, wrap the actions object in the bindActionCreators method. Check out the Redux docs page on bindActionCreators to learn more about the method.


    
import {bindActionCreators} from 'redux';
import * as notesActions from '../actions/notesActions';

const mapStateToProps = state => ({
  notes: state.notes,
});

const actions = {
  ...notesActions,
};

const mapDispatchToProps = dispatch => ({
  actions: bindActionCreators(actions, dispatch),
});     
    
  

Finally, import the home screen component and pass mapStateToProps and mapDispatchToProps to Redux’s connect method.


    
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import Home from '../screens/home';
import * as notesActions from '../actions/notesActions';

const actions = {
  ...notesActions,
};

const mapStateToProps = state => ({
  notes: state.notes,
});

const mapDispatchToProps = dispatch => ({
  actions: bindActionCreators(actions, dispatch),
});

export default connect(
  mapStateToProps,
  mapDispatchToProps,
)(Home);
    
  

Displaying Notes


Open the app/screens/home.js file and return a blank ScrollView.


    
import React, {Component} from 'react';
import {
  ScrollView,
} from 'react-native';

export default class Home extends Component {

  render() {
    return (
      <ScrollView></ScrollView>
    );
  }
}        
    
  

We should fetch our notes when the component loads using the componentDidMount method. This will make the notes available for display when the user first navigates to the view.


    
componentDidMount() {
  this.props.actions.fetchNotes();
} 
    
  

Once fetchNotes returns, our component will have access to the list of notes through this.props.notes.data. The next step is to display the notes by creating _renderNote(note) and _renderNotes() methods. These are underscore methods, which are used to improve readability by indicating that a method is internal.


_renderNote(note) takes a note and returns a <Text> component with the note’s text.


    
_renderNotes() {
  const {data} = this.props.notes;
  return <View>{data.map(note => this._renderNote(note))}</View>;
}
    
  

Now, call this.renderNotes() to display all notes in the ScrollView.


    
render() {
  return (
    <ScrollView style={styles.container}>
      {this._renderNotes()}
    </ScrollView>
  );
}
    
  

The state of the fetchNotes action is stored in the status value of our notes object. We can leverage this status data to add note loading and error states to our notes display.


When the status is failure, return <Text>{'Error'}</Text>. When the status is loading, return <Text>{'Loading'}</Text>.


    
_renderNotes() {
  const {data, status} = this.props.notes;

  if (status === 'failure') {
    return <Text>{'Error'}</Text>;
  } else if (status == 'loading') {
    return <Text>{'Loading'}</Text>;
  }

  return <View>{data.map(note => this._renderNote(note))}</View>;
}      
    
  

Handling User Input


In order to create a note, we need to gather text input from the user. Create a new internal method _renderCreateForm() that returns a TextInput component from the React Native Core.


Note: The onChangeText prop updates the component's state when a user types in an input field.


    
_renderCreateForm() {
  return (
    <View>
      <TextInput
        placeholder={'Text'}
        onChangeText={text => this.setState({text})}
        value={this.state.text}
      />
    </View>
  );
}          
    
  

The value prop is set to the internal state value text. We can initialize this value in the constructor(props) function.


    
constructor(props) {
  super(props);
  this.state = {
    text: '',
  };
}
    
  

Creating a Note


Create the Button component:


    
_renderCreateForm() {
  return (
    <View>
      <TextInput
        placeholder={'Text'}
        onChangeText={text => this.setState({text})}
        value={this.state.text}
      />
      <Button title="Create" onPress={this._createNote} />
    </View>
  );
}
    
  

Now, let's implement the _createNote function.


First, pass the note to the createNote Redux action we created in the last article. Then, set the state’s test property to a blank string to reset the TextInput component. After the createNote action returns, the component will automatically rerender when this.props.notes.status is updated.


    
_createNote = () => {
  const {text} = this.state;
  const note = {text};
  this.props.actions.createNote(note);
  this.setState({text: ''});
}
    
  

Add the _renderCreateForm method to the component’s render method to display the form.


    
render() {
  return (
    <ScrollView style={styles.container}>
      {this._renderNotes()}
      {this._renderCreateForm()}
    </ScrollView>
  );
}     
    
  

Let's add some quick styling to the TextInput and ScrollView, for aesthetic purposes. For simplicity's sake, we will declare styles in our existing file, instead of creating a new one.


Import StyleSheet from react-native.


    
import {
  ...
StyleSheet,
} from 'react-native';
    
  

Add styles.textfield to the TextInput component.


    
<TextInput
  style={styles.textfield}
  placeholder={'Text'}
  onChangeText={text => this.setState({text})}
  value={this.state.text}
/>
    
  

Add styles.container to the ScrollView component.


    
<ScrollView style={styles.container}>
  ...
</ScrollView>
    
  

Create a styles object at the bottom of the file.


    
export default class Home extends Component {  
  ...
}

const styles = StyleSheet.create({
  container: {
    marginTop: 64,
    marginHorizontal: 16,
  },
  textfield: {
    backgroundColor: '#eee',
    padding: 16,
    marginTop: 8,
  },
});
    
  

Our final file should now look like:


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

export default class Home extends Component {
  constructor(props) {
    super(props);
    this.state = {
      text: '',
    };
  }

  componentDidMount() {
    this.props.actions.fetchNotes();
  }

  _createNote = () => {
    const {text} = this.state;
    const note = {text};
    this.props.actions.createNote(note);
    this.setState({text: ''});
  }

  _renderNote(note) {
    return <Text>{note.text}</Text>;
  }

  _renderNotes() {
    const {data, status} = this.props.notes;
    if (status === 'failure') {
      return <Text>{'Error'}</Text>;
    } else if (status == 'loading') {
      return <Text>{'Loading'}</Text>;
    }
    return <View>{data.map(note => this._renderNote(note))}</View>;
  }

  _renderCreateForm() {
    return (
      <View>
        <TextInput
          style={styles.textfield}
          placeholder={'Text'}
          onChangeText={text => this.setState({text})}
          value={this.state.text}
        />
        <Button title="Create" onPress={this._createNote} />
      </View>
    );
  }

  render() {
    return (
      <ScrollView style={styles.container}>
        {this._renderNotes()}
        {this._renderCreateForm()}
      </ScrollView>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    marginTop: 64,
    marginHorizontal: 16,
  },
  textfield: {
    backgroundColor: '#eee',
    padding: 16,
    marginTop: 8,
  },
});
    
  

Run react-native run-android or react-native run-ios to preview the final result. You should now be able to see a list of notes, and have the ability to create more notes.


Check out the Github repo for this tutorial to see the completed app.


---
At FullStack Labs, we pride ourselves on our ability to push the capabilities of cutting-edge frameworks like React. Interested in learning more about speeding up development time on your next project? Contact us.

Let’s Talk!

We’d love to learn more about your project. Contact us below for a free consultation with our CEO.
Projects start at $25,000.

FullStack Labs
This field is required
This field is required
Type of project
Reason for contact:
How did you hear about us? This field is required