Just looking for advice/thoughts on the overall AccountModal and how its written. looking to achieve better written, more legible, less repetitive code, more concise, cleaner etc...
AccountScreen.js:
import React from 'react'; import { StyleSheet, ScrollView, View, Dimensions, Image } from 'react-native'; import * as Colors from '../assets/colors'; import * as Components from '../components'; import { onProfileChange } from '../actions/Profile'; import { TouchableOpacity } from 'react-native-gesture-handler'; import { connect } from 'react-redux'; const { width } = Dimensions.get('window'); class AccountScreen extends React.Component { constructor (props) { super(props); this.state = { visible: false, modalType: null, modalData: '' }; this.listener = props.onProfileChange(this.props.email); } setModal = (type) => { this.setState({ visible: !this.state.visible, modalType: type, modalData: this.props[type] }); }; renderConditionalOptions = () => { if (this.props.role === 'a') { return ( <View> {this.props.activated !== true ? ( <TouchableOpacity style={{ padding: width * 0.05, borderBottomWidth: 1, borderBottomColor: Colors.PRIMARY_OFF_WHITE }} onPress={() => this.setModal('activate')} > <Components.BodyText style={{ fontSize: 18 }} text={'Activate/De-Activate'} /> </TouchableOpacity> ) : null} <TouchableOpacity style={{ padding: width * 0.05, borderBottomWidth: 1, borderBottomColor: Colors.PRIMARY_OFF_WHITE }} onPress={() => this.setModal('adultRated')} > <Components.BodyText style={{ fontSize: 18 }} text={'Change Content Rating'} /> </TouchableOpacity> <TouchableOpacity style={{ padding: width * 0.05, borderBottomWidth: 1, borderBottomColor: Colors.PRIMARY_OFF_WHITE }} onPress={() => this.setModal('categories')} > <Components.BodyText style={{ fontSize: 18 }} text={'Modify Categories'} /> </TouchableOpacity> <TouchableOpacity style={{ padding: width * 0.05, borderBottomWidth: 1, borderBottomColor: Colors.PRIMARY_OFF_WHITE }} onPress={() => this.verificationProcess} > <Components.BodyText style={{ fontSize: 18 }} text={'Get Verified'} /> </TouchableOpacity> </View> ); } }; render () { return ( <ScrollView style={styles.container}> {/* HEADER CONTAINER CONTAINER */} <View style={styles.headerContainer}> {/* IMG / NAME ROW */} <View style={{ justifyContent: 'space-evenly', alignItems: 'center', flexDirection: 'row' }}> <TouchableOpacity onPress={() => this.setModal('profileImg')} style={{ alignItems: 'center', justifyContent: 'center', borderColor: Colors.PRIMARY_GREEN, borderWidth: 5, borderRadius: width * 0.7, backgroundColor: Colors.PRIMARY_OFF_WHITE, width: width * 0.35, height: width * 0.35 }} > <Image style={{ borderRadius: width * 0.5, height: width * 0.33, width: width * 0.33 }} source={ this.props.profileImg != '' ? ( { uri: this.props.profileImg } ) : ( require('../assets/img/white-user.png') ) } resizeMode={'contain'} /> </TouchableOpacity> <View style={{ alignItems: 'center' }}> <Components.BodyText text={this.props.legalName} style={{ fontSize: 22, fontWeight: 'bold', marginTop: width * 0.01 }} /> {/* Edit Username */} <TouchableOpacity onPress={() => this.setModal('username')} style={{ borderWidth: 1, borderColor: Colors.PRIMARY_OFF_WHITE, borderRadius: width, paddingHorizontal: width * 0.025, paddingVertical: width * 0.015, marginTop: width * 0.01 }} > <Components.BodyText text={'@' + this.props.username} style={{ textAlign: 'center', fontSize: 16 }} /> </TouchableOpacity> </View> </View> </View> {/* LOWER CONTAINER */} <View style={{ borderTopLeftRadius: 25, borderTopRightRadius: 25, borderBottomLeftRadius: 25, borderBottomRightRadius: 25, backgroundColor: 'white', width: width, marginBottom: width * 0.05 }} > <TouchableOpacity style={{ padding: width * 0.05, borderBottomWidth: 1, borderBottomColor: Colors.PRIMARY_OFF_WHITE }} onPress={() => this.setModal('email')} > <Components.BodyText text={'Update Email'} style={{ fontSize: 18 }} /> </TouchableOpacity> <TouchableOpacity style={{ padding: width * 0.05, borderBottomWidth: 1, borderBottomColor: Colors.PRIMARY_OFF_WHITE }} onPress={() => this.setModal('password')} > <Components.BodyText style={{ fontSize: 18 }} text={'Change Password'} /> </TouchableOpacity> <TouchableOpacity style={{ padding: width * 0.05, borderBottomWidth: 1, borderBottomColor: Colors.PRIMARY_OFF_WHITE }} onPress={() => this.setModal('phoneNumber')} > <Components.BodyText style={{ fontSize: 18 }} text={'Update Phone Number'} /> </TouchableOpacity> {/* CONDITIONAL OPTIONS */} {this.renderConditionalOptions()} </View> {this.state.visible === true ? ( <Components.AccountModal visible={this.state.visible} data={this.state.modalData} type={this.state.modalType} closeModal={this.setModal} navigation={this.props.navigation} /> ) : null} </ScrollView> ); } } const styles = StyleSheet.create({ container: { flex: 1, //justifyContent: 'space-between', backgroundColor: Colors.PRIMARY_OFF_WHITE }, headerContainer: { borderBottomLeftRadius: 25, borderBottomRightRadius: 25, width: width, backgroundColor: 'white', shadowColor: '#333', shadowOffset: { width: 3, height: 3 }, shadowOpacity: 0.5, shadowRadius: 10, elevation: 15, paddingVertical: width * 0.05, marginBottom: 30 } }); const mapStateToProps = ({ profile }) => { const { role, activated, profileImg, legalName, username, email, phoneNumber, bio, adultRated, categories, verified } = profile; return { role, activated, profileImg, legalName, username, email, phoneNumber, bio, adultRated, categories, verified }; }; const mapDispatchToProps = { onProfileChange }; export default connect(mapStateToProps, mapDispatchToProps)(AccountScreen); AccountModal.js:
//imports removed for brevity const { height, width } = Dimensions.get('window'); class AccountModal extends React.Component { constructor (props) { super(props); this.state = { rating: null, visible: props.visible, text: '', text2: '', showInput: false, type: '', img: '', categories: props.categories != null ? props.categories : [] }; } componentDidMount () { let first = this.props.type[0]; this.setState({ type: this.props.type.replace(/^./, first.toUpperCase()) }); } format = (input) => { let trimmed = input; trimmed = trimmed.includes(' ') ? trimmed.replace(' ', '-') : trimmed; return (trimmed = trimmed[0].toUpperCase() + trimmed.substring(1)); }; openImgPicker = () => { ImagePicker.openPicker({ width: 150, height: 150, cropping: true, cropperToolbarTitle: 'Pinch/Zoom to Crop Image', cropperCircleOverlay: true }).then((img) => { this.setState({ img: img.path }); }); }; addCategory = () => { let trimmed = this.format(this.state.text); this.setState({ categories: [ ...this.state.categories, trimmed ] }); this.refs.input.clear(); }; closeModal = () => { this.props.closeModal(); this.refs.input.clear(); this.props.resetInfo(); }; onSave = (type) => { switch (type) { case 'Bio': this.props.updateBio(this.props.docId, this.state.text); break; case 'Username': this.props.updateUsername(this.props.docId, this.state.text); break; case 'Email': this.props.updateEmail(this.props.docId, this.state.text); break; case 'Categories': this.props.updateCategories(this.props.docId, this.state.categories); break; case 'AdultRated': this.props.updateAdultRated(this.props.docId, this.state.rating); break; case 'ProfileImg': this.props.updateProfileImg({ uid: this.props.user.uid, id: this.props.docId, uri: this.state.img, url: this.props.profileImg }); break; case 'PhoneNumber': this.props.sendVerificationCode(this.state.text); this.refs.input.clear(); break; case 'ConfirmCode': this.props.compareCode( { id: this.props.verificationId, inputCode: this.state.text }, this.props.docId, this.props.phoneNumber ); case 'Verified': case 'Activate': case 'Deactivate': } }; renderModal = () => { switch (this.state.type) { case 'Bio': case 'Username': case 'Email': case 'Verified': return ( <Modal visible={this.state.visible} transparent={true} animationType={'slide'} onRequestClose={() => this.closeModal} > <View style={styles.modalContainer}> <Components.BodyText text={this.state.type + ':'} style={{ color: 'white', fontSize: 20, fontWeight: 'bold', textAlign: 'center', marginBottom: 5 }} /> <Components.BodyText text={ this.state.type === 'Selfie' ? ( '$' + this.props.selfie + ' per selfie or screenshot' ) : this.state.type === 'Video' || this.state.type === 'Voice' ? ( '$ ' + this.props[this.props.type] + ' per 5 minutes' ) : ( this.props[this.props.type] ) } style={{ fontSize: 14, marginBottom: 20, color: 'white', textAlign: 'center' }} /> <Components.TransparentInput placeholder={'Edit...'} placeholderColor={'white'} style={{ borderRadius: 20, justifyContent: 'center', height: this.props.type === 'bio' ? width * 0.25 : width * 0.1, width: width * 0.6, marginBottom: 10, padding: 5 }} ref={'input'} multiline={this.props.type === 'bio' ? true : false} onChangeText={(text) => { this.setState({ text: text.trim() }); }} /> {this.props.info != '' ? ( <Components.BodyText text={this.props.info} fontSize={14} style={{ color: 'white' }} /> ) : null} <ActivityIndicator animating={this.props.loading} color={Colors.PRIMARY_GREEN} size={'small'} /> // For specific firebase error forcing re-authentication {this.props.info.includes('sensitive') ? ( <Components.Button text={'Re-Authenticate'} type={'alert'} onPress={() => this.props.navigation.navigate('Login')} /> ) : null} <View style={{ flexDirection: 'row', justifyContent: 'space-evenly', marginTop: 10 }}> <Components.Button onPress={() => this.closeModal()} type={'secondary'} text={'Close'} fontSize={16} style={{ marginRight: 20 }} /> <Components.Button onPress={() => this.onSave(this.state.type)} type={'primary'} text={'Save'} fontSize={16} /> </View> </View> </Modal> ); case 'ProfileImg': return ( <Modal visible={this.state.visible} transparent={true} animationType={'slide'} onRequestClose={() => this.closeModal} > <View style={styles.modalContainer}> <TouchableOpacity onPress={this.openImgPicker} style={[ styles.transparentTouchable, { marginVertical: 20 } ]} > <Components.BodyText text={this.state.img != '' ? 'Image selected' : 'Tap to Select Image'} style={{ color: 'white', fontWeight: 'bold' }} /> </TouchableOpacity> {this.props.info != '' ? ( <Components.BodyText text={this.props.info} fontSize={14} style={{ color: 'white' }} /> ) : null} <ActivityIndicator animating={this.props.loading} color={Colors.PRIMARY_GREEN} size={'small'} /> <View style={{ flexDirection: 'row', justifyContent: 'space-evenly', marginTop: 10 }}> <Components.Button onPress={() => this.closeModal()} type={'secondary'} text={'Close'} fontSize={16} style={{ marginRight: 20 }} /> <Components.Button onPress={() => this.onSave(this.state.type)} type={'primary'} text={'Save'} fontSize={16} /> </View> </View> </Modal> ); case 'AdultRated': return ( <Modal visible={this.state.visible} transparent={true} animationType={'slide'} onRequestClose={() => this.setState({ visible: false })} > <View style={styles.modalContainer}> <Components.BodyText text={ this.state.rating == null ? ( 'Your current Adult Content rating: ' + '\n' + this.props.data ) : ( "You're about to set Adult-Rated for: " + '\n' + this.state.rating ) } style={{ color: 'white', textAlign: 'center', fontWeight: 'bold', fontSize: 16 }} /> <Components.Button onPress={() => this.setState({ rating: true })} type={'alert'} text={'Set for Adults'} fontSize={16} style={{ margin: 15 }} /> <Components.Button onPress={() => this.setState({ rating: false })} type={'alert'} text={'Set for Everyone'} fontSize={16} style={{ margin: 15 }} /> {this.props.info != '' ? ( <Components.BodyText text={this.props.info} fontSize={14} style={{ color: 'white' }} /> ) : null} <ActivityIndicator animating={this.props.loading} color={Colors.PRIMARY_GREEN} size={'small'} /> <View style={{ flexDirection: 'row', justifyContent: 'space-evenly', marginTop: 15 }}> <Components.Button onPress={() => this.setState({ visible: false })} type={'secondary'} text={'Close'} fontSize={16} style={{ marginRight: 20 }} /> <Components.Button onPress={() => this.onSave(this.state.type)} type={'primary'} text={'Save'} fontSize={16} /> </View> </View> </Modal> ); case 'Categories': return ( <Modal visible={this.state.visible} transparent={true} animationType={'slide'} onRequestClose={() => this.setState({ visible: false })} > <View style={styles.modalContainer}> <Components.BodyText text={this.state.type + ':'} style={{ color: 'white', fontSize: 20, fontWeight: 'bold', textAlign: 'center' }} /> <Components.BodyText text={'Tap a category to delete'} style={{ color: 'white', fontSize: 12, textAlign: 'center', marginBottom: 20 }} /> <FlatList data={this.state.categories} style={{ marginVertical: 20 }} horizontal={false} numColumns={2} keyExtractor={(item) => { return ( item.toString() + new Date().getTime().toString() + Math.floor(Math.random() * Math.floor(new Date().getTime())).toString() ); }} renderItem={({ item, index }) => ( <TouchableOpacity onPress={() => { this.setState({ categories: this.state.categories.filter((category) => { if (item != category) { return true; } else { return false; } }) }); }} style={styles.transparentTouchable} > <Components.BodyText text={index + 1 + '. ' + item} style={{ fontSize: 16, color: 'white' }} /> </TouchableOpacity> )} /> <View style={{ flexDirection: 'row', justifyContent: 'space-evenly', marginTop: 25, marginBottom: 15, alignItems: 'center' }} > <Components.TransparentInput placeholder={'Add a category...'} placeholderColor={'white'} style={{ height: width * 0.1, width: width * 0.33, marginRight: 20 }} onChangeText={(text) => { this.setState({ text }); }} returnKeyType={'done'} onSubmitEditing={() => this.addCategory()} ref={'input'} /> <TouchableOpacity onPress={() => { this.addCategory(); }} > <Image source={require('../assets/img/white-plus.png')} resizeMode={'contain'} style={{ height: width * 0.09, width: width * 0.09 }} /> </TouchableOpacity> </View> {this.props.info != '' ? ( <Components.BodyText text={this.props.info} fontSize={14} style={{ color: 'white' }} /> ) : null} <ActivityIndicator animating={this.props.loading} color={Colors.PRIMARY_GREEN} size={'small'} /> <View style={{ flexDirection: 'row', justifyContent: 'space-evenly', marginTop: 15 }}> <Components.Button onPress={() => this.closeModal()} type={'secondary'} text={'Close'} fontSize={16} style={{ marginRight: 20 }} /> <Components.Button onPress={() => this.onSave(this.state.type)} type={'primary'} text={'Save'} fontSize={16} /> </View> </View> </Modal> ); case 'Activate': return ( <Modal visible={this.state.visible} transparent={true} animationType={'slide'} onRequestClose={() => this.closeModal} > <View style={styles.modalContainer}> <Components.BodyText text={this.state.type + ' your account'} style={{ color: 'white', fontSize: 20, fontWeight: 'bold', textAlign: 'center', marginBottom: 5 }} /> <Components.BodyText text={ 'Profile must be complete to activate account. Activation status determines whether profile is accessbile in the Discover section' } style={{ color: 'white', fontSize: 14, textAlign: 'center', marginBottom: 5 }} /> {this.props.info != '' ? ( <Components.BodyText text={this.props.info} fontSize={14} style={{ color: 'white' }} /> ) : null} <ActivityIndicator animating={this.props.loading} color={Colors.PRIMARY_GREEN} size={'small'} /> <View style={{ flexDirection: 'row', justifyContent: 'center', alignItems: 'center', marginBottom: 15 }} > <Components.Button onPress={() => this.onSave('Deactivate')} type={'action'} text={'De-Activate'} fontSize={16} style={{ marginRight: 20, height: width * 0.1 }} /> <Components.Button onPress={() => this.onSave(this.state.type)} type={'primary'} text={'Activate'} fontSize={16} style={{ height: width * 0.1 }} /> </View> <Components.Button onPress={() => this.closeModal()} type={'secondary'} text={'Close'} fontSize={16} style={{ height: width * 0.1 }} /> </View> </Modal> ); case 'Password': return ( <Modal visible={this.state.visible} transparent={true} animationType={'slide'} onRequestClose={() => this.closeModal} > <View style={styles.modalContainer}> <Components.BodyText text={'Reset Password'} style={{ color: 'white', fontWeight: 'bold', fontSize: 18, textAlign: 'center' }} /> {this.props.info != '' ? ( <Components.BodyText text={this.props.info} fontSize={14} style={{ color: 'white', textAlign: 'center', marginVertical: 20 }} /> ) : null} <ActivityIndicator animating={this.props.loading} color={Colors.PRIMARY_GREEN} size={'small'} /> <View style={{ flexDirection: 'row', justifyContent: 'space-evenly', marginTop: 10 }}> <Components.Button onPress={() => this.closeModal()} type={'secondary'} text={'Close'} fontSize={16} style={{ marginRight: 20 }} /> <Components.Button onPress={() => this.props.sendPwdLink(this.props.email)} type={'primary'} text={'Reset'} fontSize={16} /> </View> </View> </Modal> ); case 'PhoneNumber': return ( <Modal visible={this.state.visible} transparent={true} animationType={'slide'} onRequestClose={() => this.closeModal} > <View style={styles.modalContainer}> <Components.BodyText text={'Update Phone Number'} style={{ color: 'white', fontWeight: 'bold', fontSize: 20 }} /> {this.props.verificationId == null ? ( <View style={{ alignItems: 'center' }}> <Components.TransparentInput placeholder={'Enter new phone number'} placeholderColor={'white'} style={{ height: width * 0.1, width: width * 0.45, marginTop: 20, marginBottom: 5 }} onChangeText={(text) => { this.setState({ text: text.trim() }); }} returnKeyType={'done'} ref={'input'} /> <Components.BodyText text={ "Remember to include a '+', your country code, followed by your number. No spaces or hyphens." } style={{ color: 'white', textAlign: 'center', paddingHorizontal: 20 }} /> {this.props.info != '' ? ( <Components.BodyText text={this.props.info} fontSize={14} style={{ marginTop: 25, color: 'white' }} /> ) : null} <ActivityIndicator animating={this.props.loading} color={Colors.PRIMARY_GREEN} size={'small'} /> <View style={{ flexDirection: 'row', justifyContent: 'space-evenly', marginTop: 15 }} > <Components.Button onPress={() => this.closeModal()} type={'secondary'} text={'Close'} fontSize={16} style={{ marginRight: 20 }} /> <Components.Button onPress={() => { this.onSave(this.state.type); }} type={'primary'} text={'Send Code'} fontSize={16} /> </View> </View> ) : ( <View style={{ alignItems: 'center' }}> <Components.TransparentInput placeholder={'Enter confirmation code'} placeholderColor={'white'} style={{ height: width * 0.1, width: width * 0.45 }} onChangeText={(text) => { this.setState({ text: text.trim() }); }} returnKeyType={'done'} ref={'input'} /> {this.props.info != '' ? ( <Components.BodyText text={this.props.info} fontSize={14} style={{ color: 'white' }} /> ) : null} <ActivityIndicator animating={this.props.loading} color={Colors.PRIMARY_GREEN} size={'small'} /> <View style={{ flexDirection: 'row', justifyContent: 'space-evenly', marginTop: 15 }} > <Components.Button onPress={() => this.closeModal()} type={'secondary'} text={'Close'} fontSize={16} style={{ marginRight: 20 }} /> <Components.Button onPress={() => this.onSave('ConfirmCode')} type={'primary'} text={'Confirm Code'} fontSize={16} /> </View> </View> )} </View> </Modal> ); } }; render () { return <View>{this.renderModal()}</View>; } } const styles = StyleSheet.create({ modalContainer: { width: width * 0.8, backgroundColor: Colors.PRIMARY_DARK, borderRadius: 20, borderWidth: 2, borderColor: Colors.PRIMARY_GREEN, alignItems: 'center', alignSelf: 'center', shadowColor: '#333', shadowOffset: { width: 30, height: 30 }, shadowOpacity: 0.9, shadowRadius: 30, elevation: 15, position: 'absolute', top: height * 0.25, paddingTop: 15, paddingBottom: 25 }, transparentTouchable: { margin: 5, borderWidth: 1, borderColor: Colors.PRIMARY_OFF_WHITE, borderRadius: width, paddingHorizontal: width * 0.025, paddingVertical: width * 0.015 } }); const mapDispatchToProps = { updateBio, resetInfo, updateUsername, updateEmail, updateCategories, updateAdultRated, updateProfileImg, updatePassword, sendPwdLink, sendVerificationCode, compareCode }; const mapStateToProps = ({ profile, auth }) => { const { activated, profileImg, legalName, username, email, phoneNumber, bio, adultRated, categories, verified, info, loading, docId, selfie, video, voice, verificationId } = profile; const { user, error } = auth; return { activated, profileImg, legalName, username, email, phoneNumber, bio, adultRated, categories, verified, info, user, loading, docId, selfie, video, voice, error, verificationId }; }; export default connect(mapStateToProps, mapDispatchToProps)(AccountModal);