DEV Community

Cover image for How about use Mediator in React Form?

How about use Mediator in React Form?

peterlits zo on April 25, 2022

EDITED Well, maybe using Mediator is not a good idea :) We can use event (or event-like) to send message from children to parent compon...
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 :)

Collapse
 
peterlitszo profile image
peterlits zo

By the way, In my opinion, the function notify is not only ask parent-component to do what, and also ask parent-component for data. For example, in sub-component Submit, we want to get the email address from parent-componet, we can use:

submit: () => {
  const email = notify("getEmail"); // or notify.getEmail();
  // request...
  console.error("cannot send because the email address is not right...");
  notify("badEmail");
}
Enter fullscreen mode Exit fullscreen mode

We do not want to let the parent have the email adress, so we cannot get the email adress by props. But we can ask parent to get it.

If you want to get it, you can also use getEmail={() => notify("getEmail")}. But longer.

Collapse
 
peterlitszo profile image
peterlits zo

If the code is using typescript, it will be better. Let us make the type of notify be a object, whose attrs are all function. Each sub-component can define what type of notify they want, and the parent must finish the union of those nofity types.

Collapse
 
peterlitszo profile image
peterlits zo

Hello, Luke Shiru. I like onXxxxxx prop. It is useful if you want to let sub-component change the parent-component's state. In here, you can use <Submit onSubmit={() => notify("submit")} onError={(errorMsg) => notify("error", errorMsg)} />. But longer.

 
peterlitszo profile image
peterlits zo

Yes yes! You are right. I will update my post! Thank you very much! :)

You are so kindly! I will just use one thread next time. This is my first time to get comment.

 
peterlitszo profile image
peterlits zo

Thanks. I agree!!! Using on is much easy.

Collapse
 
peterlitszo profile image
peterlits zo

What do you think? Do you think there is some way better than this?

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

I'm not entirely sure what makes this better than just using plain JS events.

Collapse
 
peterlitszo profile image
peterlits zo

I am not sure too. Welcome to talk about it.

I meet some trouble when I want to re-use some sub-component. When I try to detach the sub-component from the dialog, I find I need to store the state in the parent component. Because other sub-components need it.

Well, but it works. The state is hold in parent-component. And pass some 'callback' functions as sub-components' props. But it looks not good.

I hope the sub-component can hold its state into itself. I do not think that put sub-components' state into parent-component is a good idea if there are a lot of sub-components.

For example, in normal way, there is a submit button. It want to send a message to a server with some information from other sub-components. There are two way to do it:

  • Let those states put into parent-component, and pass those states as the submit button's prop.
  • Put a callback function onSubmit as the prop of the submit button. How we need write the code for submit in parent.