I Love ReactJS

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

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.

Exit mobile version