DEV Community

Discussion on: How about use Mediator in React Form?

Collapse
 
paratron profile image
Christian Engel

I have a suggestion. I'd completely remove the usage of refs to start with. They make code really hard to follow and cross-connect everything. It works fine without refs :)

Lets start with the input component. It gets everything feeded from the outside:

import React from "react";

interface Props {
    value: string;
    onChange: Function;
    error?: string;
}

function Input({value, onChange, error}: Props){
    return (
        <React.Fragment>
            <input
                type="text"
                value={value}
                onChange={onChange}
            />
            {error && <span>{error}</span>}
        </React.Fragment>
    );
}
Enter fullscreen mode Exit fullscreen mode

Nice, clean and simple. If an error gets passed to the component, it will display one. If the error is taken away, it vanishes. I'd add more things like an id, name and other things but lets leave them out for the sake of a simple example.

Lets go to the dialog next:

function Dialog({onSuccess}: {onSuccess?: Function}){
    const {
        getValue, 
        getOnChange, 
        getError, 
        submit
    } = useFormSystem("https://example.com/api", onSuccess);

    return (
        <div className={styles.dialog}>
            <Input 
                value={getValue("email")} 
                onChange={getOnChange("email")} 
                error={getError("email")} 
            />
            <input type="submit" onClick={() => submit()} />
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

The dialog uses a hook named useFormSystem which takes the target URL for the form data and maybe a callback function to call so components further up in the tree might close the dialog after successful submission.

Lets see how that custom hook is built:

function useFormSystem(apiUrl, successCallback = null, initialData = {}){
    const [data, setData] = React.uSeState(initialData);
    const [errors, setErrors] = React.useState({});

    return {
        getValue: function(key){
            return data[key];
        },
        getOnChange: function(key){
            return function(e){
                const newData = {
                    ...data,
                    [key]: e.target.value
                };
                setData(newData);
            }
        },
        getError: function(key){
            return errors[key];
        },
        submit: async function(){
            const response = await fetch(apiUrl, {
                method: "post",
                headers: {
                    "content-type": "application/json"
                },
                body: JSON.stringify(data)
            });

            if(response.status === 200){
                if(successCallback){
                    successCallback();
                }
                return;
            }

            const json = await response.json();

            setErrors(json.errors);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This is a possible approach I would choose. Everything is written from the top of my head and has not been tested but I wrote it to give a basic example without refs. And something that is highly re-usable.

It can be easily extended so you can pass a TS interface to type your complete form data here. You might want to use constants instead of writing "email" repeatingly in the getters.

You might want to use client-side validation to not being forced to send everything to the server to get some feedback.

Collapse
 
peterlitszo profile image
peterlits zo

OMG, you are right. I really like your solution. And I believe it is the really best solution! We can try to create our hook to re-use. The basic components are just renders and send event to their parent.

I will update my post! Thank you very much!

Collapse
 
paratron profile image
Christian Engel

I'm glad I could help out. My solution is just a quick sketch of what can be done with hooks. Its certainly far from being the best solution.

But I am happy I could give you some new perspective :)