Search code examples
javascriptreactjssetstate

Refactor setState to have a function inside


I need help refactoring my setState function.

I have this state:

state = {
    orderForm: {
        name: {
            elementType: 'input',
            elementConfig: {
                type: 'text',
                placeholder: 'Your Name'
            },
            value: '',
            validation: {
                required: true
            },
            valid: false,
            touched: false
        },
        street: {
            elementType: 'input',
            elementConfig: {
                type: 'text',
                placeholder: 'Street'
            },
            value: '',
            validation: {
                required: true
            },
            valid: false,
            touched: false
        },
        zipCode: {
            elementType: 'input',
            elementConfig: {
                type: 'text',
                placeholder: 'ZIP Code'
            },
            value: '',
            validation: {
                required: true,
                minLength: 5,
                maxLength: 5
            },
            valid: false,
            touched: false
        },
        country: {
            elementType: 'input',
            elementConfig: {
                type: 'text',
                placeholder: 'Country'
            },
            value: '',
            validation: {
                required: true
            },
            valid: false,
            touched: false
        },
        email: {
            elementType: 'input',
            elementConfig: {
                type: 'email',
                placeholder: 'Your E-Mail'
            },
            value: '',
            validation: {
                required: true
            },
            valid: false,
            touched: false
        },
        deliveryMethod: {
            elementType: 'select',
            elementConfig: {
                options: [
                    {value: 'fastest', displayValue: 'Fastest'},
                    {value: 'cheapest', displayValue: 'Cheapest'}
                ]
            },
            value: '',
            valid: true
        }
    },
    formIsValid: false,
    loading: false
}

And this code

:

inputChangedHandler = (event, inputIdentifier) => {
    const updatedOrderForm = {
        ...this.state.orderForm
    };
    const updatedFormElement = { 
        ...updatedOrderForm[inputIdentifier]
    };
    updatedFormElement.value = event.target.value;
    updatedFormElement.valid = this.checkValidity(updatedFormElement.value, updatedFormElement.validation);
    updatedFormElement.touched = true;
    updatedOrderForm[inputIdentifier] = updatedFormElement;

    let formIsValid = true;
    for (let inputIdentifier in updatedOrderForm) {
        formIsValid = updatedOrderForm[inputIdentifier].valid && formIsValid;
    }
    this.setState({orderForm: updatedOrderForm, formIsValid: formIsValid});
}

Please, help me refactor the function in a way that uses a function inside of a setState.

As far as I know, using it like this is not good, because setState is asynchronous and when it is based on the previous state, a function should be used inside of setState.


Solution

  • Since setState in React is asynchronous operation. Your concern is valid.

    Please have a look at the updated code for your handler event.

    inputChangedHandler = (event, inputIdentifier) => {
            this.setState(prevState => {
                const updatedOrderForm = {
                    ...prevState.orderForm,
                };
                const updatedFormElement = {
                    ...updatedOrderForm[inputIdentifier],
                };
                updatedFormElement.value = event.target.value;
                updatedFormElement.valid = this.checkValidity(updatedFormElement.value, updatedFormElement.validation);
                updatedFormElement.touched = true;
                updatedOrderForm[inputIdentifier] = updatedFormElement;
    
                let formIsValid = true;
                for (let inputIdentifier in updatedOrderForm) {
                    formIsValid = updatedOrderForm[inputIdentifier].valid && formIsValid;
                }
    
                return { orderForm: updatedOrderForm, formIsValid: formIsValid };
            });
        };
    

    Here, we pass setState a function as an argument whose argument is previous state and then return a new state object after applying the business logic.

    Hope this helps.