Search code examples
reactjskotlinkotlin-multiplatformkotlin-js

KotlinJS react-hook-form library, registering elements


Hello everyone I tried getting react-hook-form to work with KotlinJS using Dukat and Wrappers, this is what I got.

(the wrapper works fine, tried logging it in the console and it indeed is useForm from the library)

This is how I try to use the useForm hook

  val useForm = useForm<ProfileModel>()

    val register = useForm.register
    val errors = useForm.formState.errors
    val handleSubmit = useForm.handleSubmit

    val registerInstagram = register("instagram", object : validateOptions<ProfileModel, Any> {
        override var required = true
        override var maxLength = 40
    })

        form(classes = "") {
            attrs.onSubmit = {
                it.preventDefault()
                console.log("Submit")
                handleSubmit({ data, ev ->
                    console.log("On Submit " + data)
                }, { errors, ev ->
                    console.log("On submit error " + errors)
                })
            }


            input(type = InputType.text,
                classes = "$defaultInputStyle mb-4") {
                attrs.placeholder = "Name"

                register("name", object : validateOptions<ProfileModel, Any> {
                    override var required = true
                    override var maxLength = 15
                })
            }

            input(type = InputType.text,
                classes = "$defaultInputStyle mb-4") {
                this.ref = registerInstagram.ref
                attrs {
                    this.placeholder = "Instagram"
                    this.onChange = registerInstagram.onChange
                    this.onBlur = registerInstagram.onBlur
                    this.name = registerInstagram.name
                }
            }

            button(classes = defaultButtonStyle, type = ButtonType.submit) {
                +"Submit"
            }

        }
    }

useForm indeed does work and it gives me all of the right values like register, errors and such but I am unable to properly connect it to the Input element in Kotlinjs.

Register returns the following, I edited the interface to fit into the attributes of React DOM, as in regular react form hook you'd use <Input (...register) which would just apply all of these 4 things to the element.

external interface UseFormRegisterReturn {
    var onChange: ChangeEventHandler<*> // I changed this
    var onBlur: FocusEventHandler<*> // I changed this
    var ref: RefCallback<*> //I changed this
    var name: InternalFieldName
}

When I click submit I get the submit printed but the handleSubmit from the useForm isn't ran and no data is getting printed. Not sure what else can I do with this setup.

Here's the rest of the relevant Wrapper code

@JsName("useForm")
external fun <TFieldValues : FieldValues> useForm(props: `T$23`<TFieldValues> = definedExternally): UseFormReturn<TFieldValues, Any>


external interface UseFormReturn<TFieldValues : FieldValues, TContext : Any?> {
    var watch: UseFormWatch<TFieldValues>
    var getValues: UseFormGetValues<TFieldValues>
    var setError: UseFormSetError<TFieldValues>
    var clearErrors: UseFormClearErrors<TFieldValues>
    var setValue: UseFormSetValue<TFieldValues>
    var trigger: UseFormTrigger<TFieldValues>
    var formState: FormState<TFieldValues>
    var reset: UseFormReset<TFieldValues>
    var handleSubmit: UseFormHandleSubmit<TFieldValues>
    var unregister: UseFormUnregister<TFieldValues>
    var control: Control<TFieldValues, TContext>
    var register: UseFormRegister<TFieldValues>
    var setFocus: UseFormSetFocus<TFieldValues>
}

Basically, I am wondering how can I register the useForm to my elements.

EDIT: Some things you have to do

 div("w-screen h-screen items-center justify-center p-4") {
        form(classes = "") {
            attrs.onSubmit = {
                it.preventDefault() 1)

                handleSubmit({ data, ev ->
                    console.log("On Submit ", data)
                }, { errors, ev ->
                    console.log("On submit error ", errors)
                })(it) 2)
            }

            input(type = InputType.text,
                classes = "$defaultInputStyle mb-4") {
                this.ref = registerInstagram.ref
                attrs { 3)
                    this.placeholder = "Instagram" 
                    this.onChange = registerInstagram.onChange
                    this.onBlur = registerInstagram.onBlur
                    this.name = registerInstagram.name
                }
            }

            button(classes = defaultButtonStyle, type = ButtonType.submit) {
                +"Submit"
            }
  1. Prevent default (if you don't want page refresh)
  2. the function handleSubmit RETURNS A FUNCTION that needs an event as its parameter. This was missing for me, you need to call the handleSubmit()()
  3. Apply all of the stuff returned from the register function, onBlur, name onChange and ref

This at least sends the data, but validation and errors aren't still happening


Solution

  • Okay, here's the full answer with working errors and everything, hopefully this will help someone.

      val useForm = useForm<ProfileModel>()
    
        val register = useForm.register
        val errors = useForm.formState.errors.asDynamic()
        val handleSubmit = useForm.handleSubmit
    
        val registerName = register("name", js("{required: 'Name is required', maxLength: {" +
                "value: maxNameLength , message:'Max length for a name is ' + maxNameLength}}"))
    
        div("w-screen h-screen items-center justify-center p-4") {
            form(classes = "") {
                attrs.onSubmit = {
                    it.preventDefault()
    
                    handleSubmit({ data, ev ->
                        console.log("On Submit ", data)
                    }, { errors, ev ->
                        console.log("On submit error ", errors)
                    })(it)
                }
    
                input(type = InputType.text,
                    classes = "$defaultInputStyle mb-4") {
                    this.ref = registerName.ref
                    attrs {
                        this.placeholder = "Name"
                        this.onChange = registerName.onChange
                        this.onBlur = registerName.onBlur
                        this.name = "name"
                    }
                }
    
                if(jsTypeOf(errors.name) !== "undefined" ) {
                    val text = errors.name.message.toString()
                    p("ml-3 text-sm text-red-600 -mt-2 mb-2") {
                        +text
                    }
                }
    
                input(classes = defaultButtonStyle, type = InputType.submit) {
                }
    
    
    1. Prevent default (if you don't want page refresh)
    2. the function handleSubmit RETURNS A FUNCTION that needs an event as its parameter. This was missing for me, you need to call the handleSubmit()()
    3. Apply all of the stuff returned from the register function, onBlur, name onChange and ref

    To get errors to work, I had to cast errors to Dynamic, and for error messages I had to check if the error exists in the first place for that field, if it does show the error p. One issue I encountered is that you cant directly use errors.name.message but I had to add it to a variable and cast it to a string.

    For validation I changed the second parameter of register to dynamic and now I just pass js({whatever options I need}) no autocomplete and such but the JS compiler does check for mistakes while writing and it's pretty simple to write out so shouldnt pose a problem.

    EDIT: Only thing currently missing is when using this library with Typescript it will warn you that you're missing some fields in the IDE when using useForm<Type