Search code examples
react-nativereduxfloating-pointandroid-sqlitetextinput

How to map floating number from sqlite db to TextInput and backward in React Native and redux?


I'm getting a floating number from sqlite db and setting up it in redux state. Than I'm showing this property to TextInput component. To do this, I have to convert floating number to string. While editing value, I trigger event onChangeText, convert string to floating number and update redux state.

When I clear last char after point in a TextInput, my point also clearing because of converting property value from number to string. How can I save point in this case? And what's the wright way to work with floating values in react-redux?

My custom component code:

import React from 'react';
import PropTypes from 'prop-types';
import { View, TextInput } from 'react-native';

class FormFieldNumeric extends React.PureComponent {

    constructor() {
        super();
        this.state = {
            textValue: ''
        }
    }

    componentWillReceiveProps(nextProps, nextContext) {
        if (parseFloat(this.state.textValue) !== nextProps.value) {
            this.setState({
                textValue: nextProps.value ? String(nextProps.value) : ''
            })
        }
    }

    onChangeText = text => {

        if (!text) {
            this.changeValue('');
            return;
        }

        if (text.length === 1) {
            if (!'0123456789'.includes(text)) {
                return;
            }
        }

        const lastSymbol = text[text.length - 1];
        if ('1234567890.,'.includes(lastSymbol)) {

            if (text.split('').filter(ch => ch === '.' || ch === ',').length > 1) {
                return;
            }

            if (lastSymbol === ',') {
                this.changeValue(this.state.textValue + '.');
                return;
            }

            this.changeValue(text);

        }

    };

    changeValue = text => {
        this.setState({
            textValue: text
        });
        this.props.onChange(text ? parseFloat(text) : 0);
    };

    render() {

        const { caption, value, onChange, placeholder } = this.props;

        return (
            <View>
                <TextInput
                    value={this.state.textValue}
                    keyboardType="numeric"
                    onChangeText={this.onChangeText}
                    placeholder={placeholder}
                    maxLength={10}
                />
            </View>
        )

    }
}

FormFieldNumeric.propType = {
    placeholder: PropTypes.string, 
    value: PropTypes.number, 
    onChange: PropTypes.func.isRequired
};

export default FormFieldNumeric;

Solution

  • Understood what was my mistake. I fell into the anti-pattern trap when I try to keep the same state in two places. This article describes in detail this. According to the recommendations from the article, I used an uncontrolled component and stored the state directly in the component, and I only pass the property in order to initialize the value.

    import React from 'react';
    import PropTypes from 'prop-types';
    import { View, TextInput } from 'react-native';
    
    class FormFieldNumeric extends React.PureComponent {
    
        constructor(props) {
            super(props);
            this.state = {
                defaultValue: props.defaultValue,
                textValue: this.floatToTextValue(props.defaultValue)
            }
        }
    
        floatToTextValue = value => {
            return value ? String(value) : '';
        };
    
        render() {
    
            const { placeholder } = this.props;
    
            return (
                <View>
                    <TextInput
                        value={this.state.textValue}
                        keyboardType="numeric"
                        onChangeText={this.onChangeText}
                        placeholder={placeholder}
                    />
                </View>
            )
    
        }
    }
    
    FormFieldNumeric.defaultValue = {
        placeholder: '',
        defaultValue: 0
    };
    
    FormFieldNumeric.propType = {
        placeholder: PropTypes.string, 
        defaultValue: PropTypes.number, 
        onChange: PropTypes.func.isRequired
    };
    
    export default FormFieldNumeric;
    

    And for the component to update the values ​​after loading the redux state, I made the _isLoading field in the parent component, which is true by default, but becomes false after the data is loaded. I passed this value as the key property of the parent component:

    class ParentComponent extends React.PureComponent {
    
        _isLoading = false;
    
        async componentDidMount() {
            await this.props.onCreate();
            this._isLoading = false;
        }  
    
        render() {
            return (
                <View key={this._isLoading}>
                    <FormFieldNumeric
                        defaultValue={this.props.cashSum}
                        onChange={this.onChangeCashSum}
                        placeholder="0.00"
                    />
                </View>
            )
        }
    
    }