3
\$\begingroup\$

I've written this react-native component which is giving me a bad feeling that it's not as good as my expectations.

I want to make it look cleaner with smart and less code.

import React, { Component } from 'react';
import {
    SafeAreaView,
    View,
    Text,
    ScrollView,
    TextInput,
    TouchableOpacity,
    ActivityIndicator
} from 'react-native';

// Axios
const axios = require('axios').default;

// Toast
import Toast from 'react-native-simple-toast';

// Redux
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { authenticate } from '../redux/actions/Actions';

// AsyncStorage
import AsyncStorage from '@react-native-community/async-storage';

// Style
import { style, textInput, button } from '../assets/Style';

class Register extends Component {
    constructor(props) {
        super(props);

        this.state = {
            first_name: '',
            last_name: null,
            username: '',
            email: '',
            password: '',
            password_confirmation: '',
            loading: false,
            errors: {}
        };
    }

    register() {
        const { first_name, last_name, username, email, password, password_confirmation } = this.state;
        this.setState({
            loading: true,
            errors: {}
        });

        axios.post('register', {
            first_name,
            last_name,
            username,
            email,
            password,
            password_confirmation
        }).then(({data}) => {
            axios.defaults.headers.common['Authorization'] = `Bearer ${data.token}`;
            AsyncStorage.setItem('token', data.token).then(() => {
                this.props.authenticate(true, {
                    user: data.user
                });
            });
        }).catch(error => {
            if (! error.response || (error.response && error.response.status !== 422)) {
                Toast.show('An error occurred.');
            } else {
                this.setState({
                    errors: error.response.data.errors
                });
            }
        }).finally(() => {
            this.setState({ loading: false });
        });
    }

    getMessages(field) {
        const { errors } = this.state;

        if (! errors[field])
            return <></>;

        return (
            <>{ errors[field].map((item, index) => <Text style={ style.inlineDangerMessage } key={index}>{item}</Text>) }</>
        );
    }


    render() {
        const { loading, errors } = this.state;
        const opacity = loading ? 0.5 : 1;
        const disabled = loading ? true : false;
        return (
            <SafeAreaView style={ style.safeArea }>
                <ScrollView contentContainerStyle={ [style.flexCenter] }>
                    <View style={{ width: '60%' }}>
                        <TextInput style={ [textInput.default, (errors.first_name && style.isInvalidBottomBorder)] } placeholder="First name" defaultValue="" onChangeText={(text) => this.setState({ first_name: text })} />
                        { this.getMessages('first_name') }

                        <TextInput style={ [textInput.default, (errors.last_name && style.isInvalidBottomBorder), { marginTop: 10 }] } placeholder="Last name" defaultValue="" onChangeText={(text) => this.setState({ last_name: text })} />
                        { this.getMessages('last_name') }

                        <TextInput style={ [textInput.default, (errors.username && style.isInvalidBottomBorder), { marginTop: 10 }] } placeholder="Username" defaultValue="" onChangeText={(text) => this.setState({ username: text })} />
                        { this.getMessages('username') }

                        <TextInput style={ [textInput.default, (errors.email && style.isInvalidBottomBorder), { marginTop: 10 }] } placeholder="Email" defaultValue="" onChangeText={(text) => this.setState({ email: text })} />
                        { this.getMessages('email') }

                        <TextInput style={ [textInput.default, (errors.password && style.isInvalidBottomBorder), { marginTop: 10 }] } placeholder="Password" defaultValue="" secureTextEntry={true} onChangeText={(text) => this.setState({ password: text })} />
                        { this.getMessages('password') }

                        <TextInput style={ [textInput.default, (errors.password_confirmation && style.isInvalidBottomBorder), { marginTop: 10 }] } placeholder="Password confirmation" defaultValue="" secureTextEntry={true} onChangeText={(text) => this.setState({ password_confirmation: text })} />
                        { this.getMessages('password_confirmation') }

                        <TouchableOpacity style={ [button.primary, style.flexCenter, {
                            marginTop: 10,
                            height: 40,
                            opacity
                        }] } onPress={() => this.register()} disabled={disabled}>
                            { ! loading ?
                                <Text style={ [style.vazirBold, style.textCenter, { color: '#fff' }] }>Register</Text> : 
                                <ActivityIndicator size="small" color="#fff" />
                            }
                        </TouchableOpacity>

                        <TouchableOpacity style={{ marginTop: 15 }} onPress={() => this.props.navigation.goBack()} disabled={loading}>
                            <Text style={ [button.link, style.textCenter] }>I already have an account!</Text>
                        </TouchableOpacity>
                    </View>
                </ScrollView>
            </SafeAreaView>
        );
    }
}

const mapStateToProps = (state) => {
    const { redux } = state
    return { redux }
};

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

export default connect(mapStateToProps, mapDispatchToProps)(Register);
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$
  1. You don't need to use if and <></> for conditional-rendering:

      getMessages(field) {
        const { errors } = this.state;
        return (
          errors[field] && errors[field].map((item, index) => (
            <Text style={style.inlineDangerMessage} key={index}>
              {item}
            </Text>
          ))
        )
      }
    
  2. You can reuse the TextInput since there are many of them:

    • build an array of what you need

      const field = [
        { id: "1", attr: "first_name", placeholder: "First name" },
        { id: "2", attr: "last_name", placeholder: "Last name" }
        // ...
      ];
      
    • write common function

        styleFunction = attrName => {
          const { errors } = this.state;
          return [textInput.default, errors[attrName] && style.isInvalidBottomBorder];
        };
      
    • and map the repeated components

      {field.map(item => (
        <>
          <TextInput
            style={this.styleFunction(item.attr)}
            placeholder={item.placeholder}
            defaultValue=""
            onChangeText={text => this.setState({ [item.attr]: text })}
          />
          {this.getMessages(item.attr)}
        </>
      ))}
      

I'm sure there are other places where can be improved. As far as my immediate view, that's what I have got.

All the code here:

Edit objective-cdn-rhh9y

\$\endgroup\$
1
  • \$\begingroup\$ I just want to add one more thing create router classes for webservices rather than using axios everytime. \$\endgroup\$
    – Iva
    Jul 28, 2021 at 12:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.