React useReducer hook for form handling
source link: https://www.tuicool.com/articles/hit/FBZBFz2
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
I’ve started porting over a recent React project to use the new Hooks API
. Porting a few state values is relatively simple with the
useState
hook, but when it comes to larger state objects (e.g. for forms) the
useReducer
hook comes in handy.
If you’ve built with Redux
before then useReducer
is very straightforward to understand, as it uses the concept of a
Dispatcher
that handles Actions
, just like in Redux.
Let’s say we have a form that we’ve so far built using a class-based component (for brevity sake I’m not including field labels, styling, etc) :
import React from 'react' class MyForm extends React.Component { state = { name: '', address: '', age: '', favouriteSubject: '', likesChess: false, } render () { return ( <form> {Object.keys(this.state).forEach(field => ( <input key={field} type="text" value={this.state[field]} onChange={this.onChange(field)} /> ))} </form> ) } onChange = field => e => { this.setState({ [field]: e.currentTarget.value }) } }
(Yes, I could probably cache the onChange
handlers so that they don’t get recreated upon each re-render, but hooks will help us solve that below anyway!)
Here is how we would write it using useReducer
:
import React, { useReducer, useMemo, useCallback } from 'react' import Immutable from 'immutable' // the reducer (similar to what we'd have in redux) const formReducer = (state, { field, value }) => state.set(field, value) // the initial state (separated out here in case we wish to easily reset the state) const initialState = { name: '', address: '', age: '', favouriteSubject: '', likesChess: false, } // the field names (useful below) const fieldNames = Object.keys(initialState) const MyForm = () => { // get current state and the dispatcher, wrap the state as an immutable map const [ state, dispatch ] = useReducer(formReducer, new Immutable.Map(initialState)) // build the onChange handlers const handlers = fieldNames.reduce((m, field) => { // the onChange handler for this field is only re-created if the dispatch method changes m[field] = useCallback(e => dispatch({ field, value: e.currentTarget.value }), [ field, dispatch ]) return m }, {}) // convert the immutable back to an object for easy access const stateAsObj = useMemo(() => state.toObject(), [state]) return ( <form> {fieldNames.forEach(field => ( <input key={field} type="text" value={stateAsObj[field]} onChange={handlers[field]} /> ))} </form> ) }
Let’s break this down.
We only want to re-render when our state
changes, so we’ll represent it as an
Immutable.Map
instance instead of a plain Javascript object - this is similar to how we would do this if we were using Redux. This also allows us to simplify our actual reducer method to simply return the result of updating the state:
const formReducer = (state, { field, value }) => state.set(field, value)
Note: In standard Redux patterns, each dispatched Action usually has a type
key. But since our action objects are internal to this component we can just structure them however we want, and so use field
as the main key.
The
useCallback
hook is used to build a memoized onChange
callback handler for each of the text input components. The memoization is triggered/reset by the dependences we pass in the as the second parameter to the hook - the field
name and the dispatch
method:
m[field] = useCallback(e => dispatch({ field, value: e.currentTarget.value }), [ field, dispatch ])
Finally, for ease of access we convert the state
to a plain old Javascript object prior to rendering. We use the
useMemo
hook for this so that we only ever re-do this conversion if the state
has changed (another reason why using an Immutable.Map
is a good idea!):
const stateAsObj = useMemo(() => state.toObject(), [state])
And that’s all there is to it.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK