Search code examples
javascriptreactjscomponentsstate

React - .setState is partially not working


I need help with updating the state in React. I am making an Encryption/Decryption form with React. This EncryptForm is rendered in App.js. What I want to do is listen to onSubmit function and update the isSubmitted state, then render decipher value under the 'Convert' button.

My question is why .setState works in handleChange method but it doesn't work in handleSubmit method. What am I missing? (encryptMessage and decryptMessage methods are working fine.)

Here is the EncryptForm component code.

import React, { Component } from 'react'
import crypto from 'crypto'

class EncryptForm extends Component {
    state = {
        userInput: '',
        isSubmitted: false,
        decipher: ''
    }

    encryptMessage(input, key) {
        // Initialization Vector - 16 bytes
        const iv = new Buffer(crypto.randomBytes(16), 'utf8')
        const cipher = crypto.createCipheriv('aes-256-gcm', key, iv)
        let encoded = cipher.update(input, 'utf8', 'base64')
        encoded += cipher.final('base64')
        return [encoded, iv, cipher.getAuthTag()]
    }

    decryptMessage(key, encoded, iv, authTag) {
        const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv)
        decipher.setAuthTag(authTag)
        let text = decipher.update(encoded, 'base64', 'utf8')
        text += decipher.final('utf8')
        return text
    }

    /*
        Non-encryption methods
    */
    handleSubmit = event => {
        event.preventDefault()
        const KEY = new Buffer(crypto.randomBytes(32), 'utf8')
        const [encrypted, iv, authTag] = this.encryptMessage(this.state.userInput, KEY)
        const decrypted = this.decryptMessage(KEY, encrypted, iv, authTag)
        const newState = {
            ...this.state,
            isSubmitted: true,
            decipher: decrypted
        }

        // THIS IS NOW UPDATING THE STATE :(
        this.setState({ newState })
    }

    handleChange = event => {
        this.setState({
            [event.target.name]: event.target.value,
        })
    }

    render() {
        const { userInput, isSubmitted, decipher } = this.state
        const isInvalid = userInput === ''
        return (
            <form onSubmit={this.handleSubmit}>
                <input
                    type='text'
                    name='userInput'
                    placeholder='Encrypt this text...'
                    onChange={this.handleChange}
                />
                <button disabled={isInvalid} type='submit'>Convert</button>
                {isSubmitted && <p>{decipher.value}</p>}
            </form>
        )
    }
}

export default EncryptForm

Thank you!


Solution

  • You are setting state incorrectly in handleSubmit. newState is the entire state object, so setting it like this.setState({ newState }) is not updating the whole state, but instead creating a new key called newState and setting it to what you expect the state to be. The result is something like this:

    state = {
      ...previous_state,
      newState: {
        ...this.state,
        isSubmitted: true,
        decipher: decrypted
      },
    }
    

    Instead you could do something like this to correctly update:

    // desctructure so it overwrites each key
    this.setState({ ...newState });
    
    // pass the non-nested object
    this.setState(newState);
    

    Or the preferred method would be to only update the keys necessary. this.setState does a shallow merge with the given object and the previous state. So you don't need to do {...this.state} (in fact it is discouraged).

    This is the most concise and accurate way:

    this.setState({ isSubmitted: true, decipher: decrypted });