React realizes bi-directional binding of controlled components based on hooks

May 17, 2023 1362hotness 0likes 0comments

Preface

friends who know or know Vue all know that in Vue , we can achieve bidirectional data binding of controlled components through v-model , while in React , we need to achieve bidirectional data binding through value and onChange . A single can also be accepted, such as multiple.

look at an example.

	const [nickName, setNickName] = useState('')
  	const [age, setAge] = useState(null)
  
  	const handleNickNameChange = useCallback((value) => {
	    setNickName(value)
	}, [])
	
	const handleAgeChange = useCallback((value) => {
	    setAge(value)
	}, [])
  
  return (
    <>
      <Input value={nickName} onChange={handleNickNameChange} />
      <Input value={age} onChange={handleAgeChange} />
    </>
  )

according to the conclusion above, if there are multiple controlled components in a component, many will be written as above. Can we encapsulate that we only need to declare variables instead of set methods? The answer is OK, you can see below.

Tips:input is an uncontrolled component when the value of type is file, because the value is readable when type is file.

description

what I use here is React + ts . If you use js , you need to delete the type declaration after the variable.

withModel

the method component I encapsulated here is withModel , which you can name after yourself.

//withModel.tsx

import React, { forwardRef, useMemo, useCallback, useEffect } from 'react'

// two-way binding tool method

const withModel = (Component: any) => forwardRef((props, outerRef) => {

    const p =  {
      models: [],
      name: '',
      value: '',
      onChange: (event: any) => {},
      ...props,
    } 
    
    const { models = [], name, value, onChange, ...other } = p;

    const [modelValue, setModelValue] = useMemo(() => models, [models])
    
    const handleChange = useCallback((event: any) => {
          if (setModelValue) {
              const setValue = setModelValue as Function;
              setValue(event.target.value)
          }
        
          if(typeof onChange === 'function') onChange(event)
    }, [onChange])

  return (
    <Component
      {...other}
      ref={outerRef}
      name={name}
      value={modelValue !== undefined ? modelValue : value}
      onChange={handleChange}
    />
  )
})

export default withModel

input.tsx

//input.tsx

import React, { forwardRef } from "react";

import withModel from '../utils/withModel'


type inputProps = React.DetailedHTMLProps<
  React.InputHTMLAttributes<HTMLInputElement>,
  HTMLInputElement
>

const Component = forwardRef<HTMLInputElement, inputProps>((props, outerRef) => {

    const p = {...props};
    let { type } = props;
    
    if(!type) type = 'text';
    
    let element = <input ref={outerRef} {...props} />

    return (
        <>
            {!['checkbox', 'file', 'radio'].includes(type) && element}
        </>
    )
})


Component.displayName = 'Input'

export default withModel(Component );

pages use

// Page usage

import React, { useState } from "react";
import Input from "./Input"

export default () => {

    const data = useState("I am the input.")

    return (
        <>
            <Input models={data} placeholder="Please enter content" />
            {/*  */}
            <p>{`I am the input: ${data [0]} `}</p>
        </>
    )
}

Summary

principle: use forwardRef to pass the ref reference of the current controlled component, and modify it through the withModel component method.

InterServer Web Hosting and VPS

Aaron

Hello, my name is Aaron and I am a freelance front-end developer

Comments