The final thing on our list to have a usable minimum viable product is to allow each task to be marked as complete. This is where we'll create the TasksListCell
component and render that in our renderRow
function of ListView
instead of just the text.
Our goals for this component should be as follows:
- Accept text from the parent component as a prop, rendering it in
TasksListCell
- Update
listOfTasks
to take in an array of objects rather than an array of strings, allowing each object to track the name of the task and whether or not it's completed - Provide some sort of visual indicator when a task is tapped, marking it as complete both visually and within the task's
data
object, so this persists through application reloads
Let's look at how I created this component:
// Tasks/app/components/TasksList/index.js ... import TasksListCell from '../TasksListCell'; ... export default class TasksList extends Component { ... async _addTask () { const singleTask = { completed: false, text: this.state.text }
Firstly, tasks are now represented as objects within the array. This allows us to add properties to each task, such as its completed state, and leaves room for future additions.
const listOfTasks = [...this.state.listOfTasks, singleTask]; await AsyncStorage.setItem('listOfTasks', JSON.stringify(listOfTasks)); this._updateList(); } ... _renderRowData (rowData, rowID) { return ( <TasksListCell completed={ rowData.completed } id={ rowID } onPress={ (rowID) => this._completeTask(rowID) } text={ rowData.text } /> ) } ... }
The _renderRowData
method is also updated to render a new TasksListCell
component. Four props
are shared to TasksListCell
: the task's completed state, its row identifier (provided by renderRow
), a callback to alter the task's completed state, and the details of that task itself.
Here's how that TasksListCell
component was written:
// Tasks/app/components/TasksListCell/index.js import React, { Component, PropTypes } from 'react'; import { Text, TouchableHighlight, View } from 'react-native'; export default class TasksListCell extends Component { static propTypes = { completed: PropTypes.bool.isRequired, id: PropTypes.string.isRequired, onLongPress: PropTypes.func.isRequired, onPress: PropTypes.func.isRequired, text: PropTypes.string.isRequired }
Use PropTypes
to explicitly declare the data this component expects to be given. Read on for an explanation on prop validation in React.
constructor (props) { super (props); } render () { const isCompleted = this.props.completed ? 'line-through' : 'none'; const textStyle = { fontSize: 20, textDecorationLine: isCompleted };
Use a ternary operator to calculate styling for a task if it is completed.
return ( <View> <TouchableHighlight onPress={ () => this.props.onPress(this.props.id) } underlayColor={ '#D5DBDE' } > <Text style={ textStyle }>{ this.props.text }</Text> </TouchableHighlight> </View> ) } }
The preceding component provides a TouchableHighlight
for each task on the list, giving us visual opacity feedback when an item is tapped on. It also fires the _completeTask
method of TasksListCell
, which subsequently calls the onPress
prop that was passed to it and makes a visual change to the style of the cell, marking it completed with a line through the horizontal center of the task.