togglestatereact-nativeredundancy

Eliminating extreme redundancies in User Onboarding Keyword Selection


In the code below, I am attempting to provide an onboarding component that enables the user to select out keywords they are interested in. In doing so, a function is triggered that changes the state of that keyword to true, and changes the color of the component to a color that indicates that that keyword is in fact selected.

The problem is that my current code is very redundant in naming these keywords as state (which are also in an array in the same code file). Is there anyway to do this dynamically, in which there is a state array of selected keywords rather than 30 states indicated by each keyword that change when pressed?

var React = require('react-native');
var {
    View, 
    ScrollView, 
    Image,
    StyleSheet,
    Text, 
    TouchableHighlight,
} = React;

//additional libraries
var Parse = require('parse/react-native'); //parse for data storage
Icon = require('react-native-vector-icons/Ionicons'); //vector icons
var LinearGradient = require('react-native-linear-gradient'); //linear grad. over button

//dimensions
var Dimensions = require('Dimensions');
var window = Dimensions.get('window');

//dynamic variable components
var ImageButton = require('../common/imageButton');
var KeywordBox = require('./onboarding/keyword-box');
var ActionButton = require('../common/ActionButton');

module.exports = React.createClass({
    getInitialState: function() {
        return {
            isFinished: false,
            BlackLivesMatter: false,
            Adventure_Travel: false,
            Arts: false,
            Auto: false,
            Basketball: false,
            Book_Reviews: false,
            Business: false,
            Celebrity_News: false,
            Computers: false,
            Design: false,
            Entertainment: false,
            Entrepreneurship: false,
            Fashion_Trends: false,
            Film: false,
            Happiness: false,
            Health: false,
            Hip_Hop: false,
            History: false,
            Humor: false,
            Internet: false,
            LGBQT: false,
            Management: false,
            Marketing: false,
            Mobile_Games: false,
            Music: false,
            News: false,
            Performing_Arts: false,
            Photography: false,
            Politics: false,
            Sports: false,
            Technology: false,
            Travel: false,
            Trending: false,
            Universe: false,
            Women_of_Color: false,
            World: false,
        };
    }, 
    render: function() {
        return (
            <View style={[styles.container]}>
                <Image 
                    style={styles.bg} 
                    source={require('./img/login_bg1_3x.png')}>
                    <View style={[styles.header, this.border('red')]}>
                        <View style={[styles.headerWrapper]} >
                            <Image 
                                resizeMode={'contain'}
                                style={[styles.onboardMsg]}
                                source={require('./img/onboard_msg.png')} >
                            </Image>
                        </View>
                    </View>
                    <View style={[styles.footer, this.border('blue')]}>
                        <ScrollView 
                            showsVerticalScrollIndicator={false}
                            showsHorizontalScrollIndicator={false}
                            horizontal={false}
                            style={styles.footerWrapperNC}
                            contentContainerStyle={[styles.footerWrapper]}>
                            {this.renderKeywordBoxes()}
                        </ScrollView>
                    </View>
                </Image>
                <ActionButton 
                    onPress={this.onNextPress}
                    buttonColor="rgba(0,0,0,0.7)" />
            </View>
        );
    }, 
    renderKeywordBoxes: function() {
        //renders array of keywords in keyword.js
        //and maps them onto custom component keywordbox to show in the onboarding
        //component
        var Keywords = ['LGBQT', 'BlackLivesMatter', 'Arts', 'Hip-Hop', 'History', 
        'Politics', 'Fashion Trends', 'Entrepreneurship', 'Technology', 'Business', 
        'World', 'Health', 'Trending', 'Music', 'Sports', 'Entertianment', 
        'Mobile Games', 'Design', 'News', 'Humor', 'Happiness', 'Women of Color', 'Travel',
        'Photography','Computers', 'Universe', 'Internet','Performing Arts','Management',
         'Celebrity News', 'Book Reviews', 'Marketing', 'Basketball', 'Film', 'Adventure Travel', 
         'Auto'];

        return Keywords.map(function(keyword, i) {
            return <KeywordBox 
                key={i} text={keyword} 
                onPress={ () => { this.onKeywordPress(keyword) }}
                selected={this.state.+keyword}/>
        });
    }, 
    onKeywordPress: function(keyword) {

        //take the spaces out the word
        keyword = keyword.split(' ').join('_');
        keyword = keyword.split('-').join('_');


        //change the state accordingly without doing so directly 
        let newState = {...this.state};
        newState[keyword] = !newState[keyword];
        this.setState(newState);
        console.log(keyword, newState);
    }, 
    onNextPress: function() {

    }, 
    //function that helps with laying out flexbox itmes 
    //takes a color argument to construct border, this is an additional 
    //style because we dont want to mess up our real styling 
     border: function(color) {
        return {
          //borderColor: color, 
          //borderWidth: 4,
        } 
     },
});

styles = StyleSheet.create({
    header: {
        flex: 2,
    }, 
    headerWrapper: {
        flex: 1, 
        flexDirection: 'column', 
        alignItems: 'center',
        justifyContent:'space-around',
        marginTop: window.height/35,
    },
    onboardMsg: {
        width: (window.width/1.2), 
        height: (452/1287)*((window.width/1.2)),
    },
    footer: {
        flex: 7, 
        marginTop: window.height/35,
        marginLeft: window.width/30,
    }, 
    //container style wrapper for scrollview
    footerWrapper: {
        flexWrap: 'wrap', 
        alignItems: 'flex-start',
        flexDirection:'row',
    },
    //non-container style wrapper for scrollview
    footerWrapperNC: {
        flexDirection:'column',
    },
    container: {
        flex: 1, 
        alignItems: 'center', 
        justifyContent: 'center',
    }, 
    bg: {
        flex: 1,
        width: window.width, 
        height: window.height, 
    },
});

which looks like this:

enter image description here


Solution

  • Determined answer in the following subset of code. Made a state array and updated a temp array that has the same boolean states as the state array to avoid direct manipulation (outside of setState):

    var React = require('react-native');
    var {
        View, 
        ScrollView, 
        Image,
        StyleSheet,
        Text, 
        TouchableHighlight,
    } = React;
    
    //additional libraries
    var Parse = require('parse/react-native'); //parse for data storage
    Icon = require('react-native-vector-icons/Ionicons'); //vector icons
    var LinearGradient = require('react-native-linear-gradient'); //linear grad. over button
    
    //need react-addons-update to use immutability helpers*******
    
    //dimensions
    var Dimensions = require('Dimensions');
    var window = Dimensions.get('window');
    
    //dynamic variable components
    var ImageButton = require('../common/imageButton');
    var KeywordBox = require('./onboarding/keyword-box');
    var ActionButton = require('../common/ActionButton');
    
    module.exports = React.createClass({ 
        getInitialState: function() {
            return {
                keywords_array: Array.apply(null, Array(37)).map(Boolean.prototype.valueOf,false)
            };
        }, 
        render: function() {
            var newData = this.state.keywords_array;
            return (
                <View style={[styles.container]}>
                    <Image 
                        style={styles.bg} 
                        source={require('./img/login_bg1_3x.png')}>
                        <View style={[styles.header, this.border('red')]}>
                            <View style={[styles.headerWrapper]} >
                                <Image 
                                    resizeMode={'contain'}
                                    style={[styles.onboardMsg]}
                                    source={require('./img/onboard_msg.png')} >
                                </Image>
                            </View>
                        </View>
                        <View style={[styles.footer, this.border('blue')]}>
                            <ScrollView 
                                showsVerticalScrollIndicator={false}
                                showsHorizontalScrollIndicator={false}
                                horizontal={false}
                                style={styles.footerWrapperNC}
                                contentContainerStyle={[styles.footerWrapper]}>
                                {this.renderKeywordBoxes(newData)}
                            </ScrollView>
                        </View>
                    </Image>
                    <ActionButton 
                        onPress={this.onNextPress}
                        buttonColor="rgba(0,0,0,0.7)" />
                </View>
            );
        }, 
        renderKeywordBoxes: function(newData) {
            //renders array of keywords in keyword.js
            //and maps them onto custom component keywordbox to show in the onboarding
            //component
            var Keywords = ['LGBQT', 'BlackLivesMatter', 'Arts', 'Hip-Hop', 'History', 
            'Politics', 'Fashion Trends', 'Entrepreneurship', 'Technology', 'Business', 
            'World', 'Health', 'Trending', 'Music', 'Sports', 'Entertianment', 
            'Mobile Games', 'Design', 'News', 'Humor', 'Happiness', 'Women of Color', 'Travel',
            'Photography','Computers', 'Universe', 'Internet','Performing Arts','Management',
             'Celebrity News', 'Book Reviews', 'Marketing', 'Basketball', 'Film', 'Adventure Travel', 
             'Auto'];
    
             var that = this;
    
            return Keywords.map(function(keyword, i) {
    
                return <KeywordBox 
                    key={i} 
                    text={keyword} 
                    onPress={ () => { that.onKeywordPress(i, keyword, newData) }}
                    selected={newData[i]} />
            });
        }, 
    
        onKeywordPress: function(i, keyword, newData) {
            console.log(this.state.keywords_array);
            console.log(keyword); 
            console.log(newData); 
    
            //change the state accordingly without doing so directly 
            var array = newData;
            array[i] = true; 
            this.setState({
              keywords_array: array
            });
    
            console.log(array);
            console.log(this.state.keywords_array);
    
        }, 
        onNextPress: function() {
    
        }, 
        //function that helps with laying out flexbox itmes 
        //takes a color argument to construct border, this is an additional 
        //style because we dont want to mess up our real styling 
         border: function(color) {
            return {
              //borderColor: color, 
              //borderWidth: 4,
            } 
         },
    });
    
    styles = StyleSheet.create({
        header: {
            flex: 2,
        }, 
        headerWrapper: {
            flex: 1, 
            flexDirection: 'column', 
            alignItems: 'center',
            justifyContent:'space-around',
            marginTop: window.height/35,
        },
        onboardMsg: {
            width: (window.width/1.2), 
            height: (452/1287)*((window.width/1.2)),
        },
        footer: {
            flex: 7, 
            marginTop: window.height/35,
            marginLeft: window.width/30,
        }, 
        //container style wrapper for scrollview
        footerWrapper: {
            flexWrap: 'wrap', 
            alignItems: 'flex-start',
            flexDirection:'row',
        },
        //non-container style wrapper for scrollview
        footerWrapperNC: {
            flexDirection:'column',
        },
        container: {
            flex: 1, 
            alignItems: 'center', 
            justifyContent: 'center',
        }, 
        bg: {
            flex: 1,
            width: window.width, 
            height: window.height, 
        },
    });