DEV Community

Damien Cosset
Damien Cosset

Posted on

Refactor a form with React Hooks and useState

Introduction

React Hooks are one of those things I decided I would look at later. I've read and heard great things about it, so I later is now. I had a component with a form that I thought could be refactored using hooks, so I started with that. Always easier to begin with small steps.

Before

Nothing fancy, we use the material-ui framework to create a Dialog component. Then we have three TextFields ( text inputs ) inside of it:


export default class AddItemPopup extends React.Component {

    constructor(props){
        super(props)
        this.state = {
            name: '',
            quantity: 0,
            unitCost: 0
        }
    }

    handleInputChange = e => {
        const {name, value} = e.target
        this.setState({
            [name]: value
        })
    }

    addItem = () => {
        const {name, quantity, unitCost} = this.state

        if(!name || !quantity || !unitCost) return

        this.props.saveItem(this.state)
    }

    render(){

        const {open, closePopup} = this.props
        const {name, quantity, unitCost} = this.state
        return(
            <Dialog 
                open={open}
                onClose={closePopup}>
                <DialogTitle>Add new item</DialogTitle>
                <DialogContent>
                    <TextField 
                        name='name'
                        label='Item name/Description'
                        onChange={this.handleInputChange}
                        value={name}/>
                    <TextField 
                        name='quantity'
                        label='Quantity'
                        onChange={this.handleInputChange}
                        value={quantity}/>
                    <TextField 
                        name='unitCost'
                        label='Unit Cost'
                        onChange={this.handleInputChange}
                        value={unitCost}/>
                </DialogContent>
                <DialogActions>
                    <Button onClick={closePopup} color="secondary" variant="contained">
                        Cancel
                    </Button>
                    <Button onClick={this.addItem} color="primary" variant="contained">
                            Save
                    </Button>
                </DialogActions>
            </Dialog>
        )
    }
}

I saved you the imports at the top of the file, but you got the idea. A class component with a form and a state to keep track of the form inputs' values. Now, let's rewrite this component by using the useState hook.

// Import the hook first
import React, {useState} from 'react'

const AddItemPopup = ({
    open, 
    closePopup,
    saveItem
}) => {

    const handleInputChange = e => {
        const {name, value} = e.target
        setValues({...values, [name]: value})
    }

    const addItem = () => {
        const {name, quantity, unitCost} = values

        if(!name || !quantity || !unitCost) return

        saveItem(values)
    }
        // Declare our state variable called values
        // Initialize with our default values

    const [values, setValues] = useState({name: '', quantity: 0, unitCost: 0})
    return(
        <Dialog 
        open={open}
        onClose={closePopup}>
        <DialogTitle>Add new item</DialogTitle>
            <DialogContent>
                <TextField 
                    name='name'
                    label='Item name/Description'
                    onChange={handleInputChange}
                    value={values.name}/>
                <TextField 
                    name='quantity'
                    label='Quantity'
                    onChange={handleInputChange}
                    value={values.quantity}/>
                <TextField 
                    name='unitCost'
                    label='Unit Cost'
                    onChange={handleInputChange}
                    value={values.unitCost}/>
            </DialogContent>
            <DialogActions>
                <Button onClick={closePopup} color="secondary" variant="contained">
                    Cancel
                </Button>
                <Button onClick={addItem} color="primary" variant="contained">
                        Save
                </Button>
            </DialogActions>
        </Dialog>
    )
}

export default AddItemPopup

BOOM! Our component became a function now. What did we do:

  • useState returns two things: the current state ( here as values ) and a function that lets you update it ( here as setValues )
  • useState takes one argument: the initial state.
  • The onChange handler function now uses this setValues function to modify the internal state of the component. As you can see, the values variable is accessible everywhere is the component.

Note: We could have used three different hooks to update each input separately, whatever you think might be more readable to you ;)

Top comments (9)

Collapse
 
saulsilver profile image
Hatem Houssein

Merci beaucoup pour l'explication, really helpful comparison!

Is there a reason why you initialized setValues after the handleInputChange() and AddItem() functions, or is it just a personal preference?

Collapse
 
damcosset profile image
Damien Cosset

When I first started with hooks, I declared them last. Nowadays, I do declare them first :D
Personal preferences ๐Ÿ˜‰

Collapse
 
saulsilver profile image
Hatem Houssein

Yeah Dan (from React) suggested declaring them at the top so I do the same and was just wondering.

Thanks!

Collapse
 
bluebill1049 profile image
Bill

Hey Damien,

Nice show case of using setState (hook) for form validation, i have build a form hook

Repo: github.com/bluebill1049/react-hook...
Website: react-hook-form.now.sh

check it out, i think you may find it useful :)

cheers
bill

Collapse
 
giovannipds profile image
Giovanni Pires da Silva • Edited

Hi Damien! Just saw your approach and applied in a new form I was making at work. Seems like the performance went down. As I'm supposing, having one unique state for all the inputs may dispatch multiple component (input) renders, since all the states are being changed every time when just one's changing. Isn't the performance better having separated hooks for each inputs? Seems like's not just about it being more readable or not.

Collapse
 
damcosset profile image
Damien Cosset

Hello!
I should have explored the performance aspect of the refactoring! I didn't consider that when I wrote that article unfortunately. I would recommend having your state variables separated when using hooks. You can find more informations on the React docs right here.

Collapse
 
giovannipds profile image
Giovanni Pires da Silva

It'd probably be cooler to just merge handleInputChange as you did but keep hooks separated.

Collapse
 
giovannipds profile image
Giovanni Pires da Silva

Pardon! I can be wrong, just tested a little stuff here and saw that my whole page was being loaded. Seems like not be a problem of this technique itself but an issue of my page/project/partial. Thank you for sharing your knowledge!

Collapse
 
ramanpreet6532 profile image
Raman

Hi, can you please tell me, how can I clear the input field using useState hook on a button click...I am a beginner at this..can you share with me any sample or tell me something please

Thanks