Bootcamp Notes – Day 7 (Wed) – React Native: Week 3: Gestures, and More!

Bootcamp Notes – Day 7 (Wed) – React Native: Week 3: Gestures, and More!

Gestures with React Native

Performing gestures is a natural way for users to interact with a mobile app.

Many types of gestures: simple/swipe gestures, quick taps, simultaneous multi-finger touches, etc

Built-in PanResponder APi: highly customizable and powerful way to manage responses to gestures throughout a gesture’s entire lifecycle, from the start of the gesture to the end.

The word “pan” in this context simply means movement in a direction such as panning the camera to the right or to the left.

In Gestures Part 1, we will look at handling a single touch gesture on the CampesiteInfo component, a drag from right to left similar to the swipeout we implemented previously. But we are not limited to responding to the gesture by revealing buttons, we now have more flexibility to customize our resonse.


Gestures Part 1

CampsiteInfoComponent.js

import React, { Component } from ‘react’;
import { Text, View, ScrollView, FlatList, Modal, Button, StyleSheet, Alert, PanResponder } from ‘react-native’;
import { Card, Icon, Rating, Input } from ‘react-native-elements’;
import { connect } from ‘react-redux’;
import { baseUrl } from ‘../shared/baseUrl’;
import { postFavorite, postComment } from ‘../redux/ActionCreators’;
import * as Animatable from ‘react-native-animatable’;
const mapStateToProps = state => {
    return {
        campsites: state.campsites,
        comments: state.comments,
        favorites: state.favorites
    };
};
const mapDispatchToProps = {
    postFavorite: campsiteId => (postFavorite(campsiteId)),
    postComment: (campsiteId, rating, author, text) => postComment(campsiteId, rating, author, text)
};
function RenderCampsite(props) {
    const {campsite} = props;
    const recognizeDrag = ({dx}) => (dx < -200) ? true : false;
    const panResponder = PanResponder.create({
        onStartShouldSetPanResponder: () => true,
        onPanResponderEnd: (e, gestureState) => {
            console.log(‘pan responder end’, gestureState);
            if (recognizeDrag(gestureState)) {
                Alert.alert(
                    ‘Add Favorite’,
                    ‘Are you sure you wish to add ‘ + campsite.name + ‘ to favorites?’,
                    [
                        {
                            text: ‘Cancel’,
                            style: ‘cancel’,
                            onPress: () => console.log(‘Cancel Pressed’)
                        },
                        {
                            text: ‘OK’,
                            onPress: () => props.favorite ?
                                console.log(‘Already set as a favorite’) : props.markFavorite()
                        }
                    ],
                    { cancelable: false }
                );
            }
            return true;
        }
    });
    if (campsite) {
        return (
            <Animatable.View
                animation=’fadeInDown’
                duration={2000}
                delay={1000}
                {…panResponder.panHandlers}>
            <Card 
                featuredTitle={campsite.name}
                image={{uri: baseUrl + campsite.image}}>
            
                <Text style={{margin: 10}}>
                    {campsite.description}
                </Text>
                <View style={styles.cardRow}>
                    <Icon
                        name={props.favorite ? ‘heart’ : ‘heart-o’}
                        type=’font-awesome’
                        color=’#f50′
                        raised
                        reverse
                        onPress={() => props.favorite ? 
                            console.log(‘Already set as a favorite’) : props.markFavorite()}
                    />
                    <Icon
                        name=’pencil’
                        type=’font-awesome’
                        color=’#5637DD’
                        raised
                        reverse
                        onPress={() => props.onShowModal()} 
                            // console.log(‘Already set as a favorite’) : props.markFavorite()}
                    />
                </View>
            </Card>
            </Animatable.View>
        );
    }
    return <View />;
}
function RenderComments({comments}) {
    const renderCommentItem = ({item}) => {
        return (
            <View style={{margin: 10}}>
                <Text style={{fontSize: 14}}>{item.text}</Text>
                <Rating
                        startingValue={item.rating}
                        imageSize={10}
                        style={{paddingVertical: ‘5%’, alignItems: ‘flex-start’}}
                        readonly
                    />
                <Text style={{fontSize: 12}}>{`– ${item.author}, ${item.date}`}</Text>
            </View>
        );
    };
    return (
        <Animatable.View animation=’fadeInUp’ duration={2000} delay={1000}>
            <Card title=’Comments’>
                <FlatList
                    data={comments}
                    renderItem={renderCommentItem}
                    keyExtractor={item => item.id.toString()}
                />
            </Card>
        </Animatable.View>
    );
}
class CampsiteInfo extends Component {
    constructor(props) {
        super(props);
        this.state = {
            showModal: false,
            rating: 5,
            author: ” “,
            text: ” “
        };
    }
    toggleModal() {
        this.setState({showModal: !this.state.showModal});
    }
    handleComment(campsiteId) {
        this.props.postComment(campsiteId, this.state.rating, this.state.author, this.state.text);
        this.toggleModal();
    }
    resetForm() {
        this.setState({
            showModal: false,
            rating: 5,
            author: ” “,
            text: ” “
        });
    }
    markFavorite(campsiteId) {
        this.props.postFavorite(campsiteId);
    }
    static navigationOptions = {
        title: ‘Campsite Information’
    }
    render() {
        const campsiteId = this.props.navigation.getParam(‘campsiteId’);
        const campsite = this.props.campsites.campsites.filter(campsite => campsite.id === campsiteId)[0];
        const comments = this.props.comments.comments.filter(comment => comment.campsiteId === campsiteId);
        return (
            <ScrollView>
                <RenderCampsite campsite={campsite}
                    favorite={this.props.favorites.includes(campsiteId)}
                    markFavorite={() => this.markFavorite(campsiteId)}
                    onShowModal={() => this.toggleModal(campsiteId)}
                />
                <RenderComments comments={comments} />
                <Modal
                    animationType={‘slide’}
                    transparent={false}
                    visible={this.state.showModal}
                    onRequestClose={() => this.toggleModal()}
                >
                    <View style={styles.modal}>
                    <Rating
                        showRating
                        startingValue={this.state.rating}
                        imageSize={40}
                        onFinishRating={rating => this.setState({rating: rating})} 
                        style={{paddingVertical: 10}}
                    />
                    <Input
                        placeholder=’Author’
                        leftIcon={{ type: “font-awesome”, name: “user-o”}}
                        leftIconContainerStyle={{paddingRight: 10}}
                        onChangeText={value => this.setState({author: value})}
                        value={this.state.author}
                        />
                    <Input
                        placeholder=’Comment’
                        leftIcon={{ type: “font-awesome”, name: “comment-o”}}
                        leftIconContainerStyle={{paddingRight: 10}}
                        onChangeText={value => this.setState({text: value})}
                        value={this.state.text}
                        />
                        <View style={{margin: 10}}>
                        <Button
                            onPress={() => {
                                this.handleComment(campsiteId);
                                this.resetForm();
                            }}
                            color=’#5637DD’
                            title=’Submit’
                        />
                        </View>
                        <View style={{margin: 10}}>
                        <Button
                            onPress={() => {
                                this.toggleModal();
                                this.resetForm();
                            }}
                            color=’#808080′
                            title=’Cancel’
                        />
                        </View>
                    </View>
                </Modal>
            </ScrollView>
        );
    }
}
const styles = StyleSheet.create({
    cardRow: {
        alignItems: ‘center’,
        justifyContent: ‘center’,
        flex: 1,
        flexDirection: ‘row’,
        margin: 20
    },
    modal: { 
        justifyContent: ‘center’,
        margin: 20
    }
});
export default connect(mapStateToProps, mapDispatchToProps)(CampsiteInfo);

 


Gestures Part 2

CampsiteInfoComponent.js

import React, { Component } from ‘react’;
import { Text, View, ScrollView, FlatList, Modal, Button, StyleSheet, Alert, PanResponder } from ‘react-native’;
import { Card, Icon, Rating, Input } from ‘react-native-elements’;
import { connect } from ‘react-redux’;
import { baseUrl } from ‘../shared/baseUrl’;
import { postFavorite, postComment } from ‘../redux/ActionCreators’;
import * as Animatable from ‘react-native-animatable’;
const mapStateToProps = state => {
    return {
        campsites: state.campsites,
        comments: state.comments,
        favorites: state.favorites
    };
};
const mapDispatchToProps = {
    postFavorite: campsiteId => (postFavorite(campsiteId)),
    postComment: (campsiteId, rating, author, text) => postComment(campsiteId, rating, author, text)
};
function RenderCampsite(props) {
    const {campsite} = props;
    const view = React.createRef();
    const recognizeDrag = ({dx}) => (dx < -200) ? true : false;
    const panResponder = PanResponder.create({
        onStartShouldSetPanResponder: () => true,
        onPanResponderGrant: () => {
            view.current.rubberBand(1000)
            .then(endState => console.log(endState.finished ? ‘finished’ : ‘canceled’));
        },
        onPanResponderEnd: (e, gestureState) => {
            console.log(‘pan responder end’, gestureState);
            if (recognizeDrag(gestureState)) {
                Alert.alert(
                    ‘Add Favorite’,
                    ‘Are you sure you wish to add ‘ + campsite.name + ‘ to favorites?’,
                    [
                        {
                            text: ‘Cancel’,
                            style: ‘cancel’,
                            onPress: () => console.log(‘Cancel Pressed’)
                        },
                        {
                            text: ‘OK’,
                            onPress: () => props.favorite ?
                                console.log(‘Already set as a favorite’) : props.markFavorite()
                        }
                    ],
                    { cancelable: false }
                );
            }
            return true;
        }
    });
    if (campsite) {
        return (
            <Animatable.View
                animation=’fadeInDown’
                duration={2000}
                delay={1000}
                ref={view}
                {…panResponder.panHandlers}>
            <Card 
                featuredTitle={campsite.name}
                image={{uri: baseUrl + campsite.image}}>
            
                <Text style={{margin: 10}}>
                    {campsite.description}
                </Text>
                <View style={styles.cardRow}>
                    <Icon
                        name={props.favorite ? ‘heart’ : ‘heart-o’}
                        type=’font-awesome’
                        color=’#f50′
                        raised
                        reverse
                        onPress={() => props.favorite ? 
                            console.log(‘Already set as a favorite’) : props.markFavorite()}
                    />
                    <Icon
                        name=’pencil’
                        type=’font-awesome’
                        color=’#5637DD’
                        raised
                        reverse
                        onPress={() => props.onShowModal()} 
                            // console.log(‘Already set as a favorite’) : props.markFavorite()}
                    />
                </View>
            </Card>
            </Animatable.View>
        );
    }
    return <View />;
}
function RenderComments({comments}) {
    const renderCommentItem = ({item}) => {
        return (
            <View style={{margin: 10}}>
                <Text style={{fontSize: 14}}>{item.text}</Text>
                <Rating
                        startingValue={item.rating}
                        imageSize={10}
                        style={{paddingVertical: ‘5%’, alignItems: ‘flex-start’}}
                        readonly
                    />
                <Text style={{fontSize: 12}}>{`– ${item.author}, ${item.date}`}</Text>
            </View>
        );
    };
    return (
        <Animatable.View animation=’fadeInUp’ duration={2000} delay={1000}>
            <Card title=’Comments’>
                <FlatList
                    data={comments}
                    renderItem={renderCommentItem}
                    keyExtractor={item => item.id.toString()}
                />
            </Card>
        </Animatable.View>
    );
}
class CampsiteInfo extends Component {
    constructor(props) {
        super(props);
        this.state = {
            showModal: false,
            rating: 5,
            author: ” “,
            text: ” “
        };
    }
    toggleModal() {
        this.setState({showModal: !this.state.showModal});
    }
    handleComment(campsiteId) {
        this.props.postComment(campsiteId, this.state.rating, this.state.author, this.state.text);
        this.toggleModal();
    }
    resetForm() {
        this.setState({
            showModal: false,
            rating: 5,
            author: ” “,
            text: ” “
        });
    }
    markFavorite(campsiteId) {
        this.props.postFavorite(campsiteId);
    }
    static navigationOptions = {
        title: ‘Campsite Information’
    }
    render() {
        const campsiteId = this.props.navigation.getParam(‘campsiteId’);
        const campsite = this.props.campsites.campsites.filter(campsite => campsite.id === campsiteId)[0];
        const comments = this.props.comments.comments.filter(comment => comment.campsiteId === campsiteId);
        return (
            <ScrollView>
                <RenderCampsite campsite={campsite}
                    favorite={this.props.favorites.includes(campsiteId)}
                    markFavorite={() => this.markFavorite(campsiteId)}
                    onShowModal={() => this.toggleModal(campsiteId)}
                />
                <RenderComments comments={comments} />
                <Modal
                    animationType={‘slide’}
                    transparent={false}
                    visible={this.state.showModal}
                    onRequestClose={() => this.toggleModal()}
                >
                    <View style={styles.modal}>
                    <Rating
                        showRating
                        startingValue={this.state.rating}
                        imageSize={40}
                        onFinishRating={rating => this.setState({rating: rating})} 
                        style={{paddingVertical: 10}}
                    />
                    <Input
                        placeholder=’Author’
                        leftIcon={{ type: “font-awesome”, name: “user-o”}}
                        leftIconContainerStyle={{paddingRight: 10}}
                        onChangeText={value => this.setState({author: value})}
                        value={this.state.author}
                        />
                    <Input
                        placeholder=’Comment’
                        leftIcon={{ type: “font-awesome”, name: “comment-o”}}
                        leftIconContainerStyle={{paddingRight: 10}}
                        onChangeText={value => this.setState({text: value})}
                        value={this.state.text}
                        />
                        <View style={{margin: 10}}>
                        <Button
                            onPress={() => {
                                this.handleComment(campsiteId);
                                this.resetForm();
                            }}
                            color=’#5637DD’
                            title=’Submit’
                        />
                        </View>
                        <View style={{margin: 10}}>
                        <Button
                            onPress={() => {
                                this.toggleModal();
                                this.resetForm();
                            }}
                            color=’#808080′
                            title=’Cancel’
                        />
                        </View>
                    </View>
                </Modal>
            </ScrollView>
        );
    }
}
const styles = StyleSheet.create({
    cardRow: {
        alignItems: ‘center’,
        justifyContent: ‘center’,
        flex: 1,
        flexDirection: ‘row’,
        margin: 20
    },
    modal: { 
        justifyContent: ‘center’,
        margin: 20
    }
});
export default connect(mapStateToProps, mapDispatchToProps)(CampsiteInfo);

 


Additional Resources: