Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Form Research #1329

Open
iljs opened this issue Jul 30, 2024 · 16 comments
Open

Form Research #1329

iljs opened this issue Jul 30, 2024 · 16 comments
Assignees

Comments

@iljs
Copy link
Contributor

iljs commented Jul 30, 2024

Form Research

Есть 2 основных предложения реализации:

  • Поддержка хука useForm
  • Сделать компонент Form

1. Для поддержки хука useForm, каждый компонент должен поддерживать параметры:

  • ref
  • name
  • onChange
  • onBlur

Важно предусмотреть типы, который возвращаются при onChange. Например текущий select ( https://plasma.sberdevices.ru/web/components/select/ ) возвращает просто value. В свою очередь для работы с формой надо возвращать тип, который есть в обычном нативном select из html ( https://react.dev/reference/react-dom/components/select ) в MUI сделано именно так ( https://mui.com/material-ui/api/select/ )

Анализ разработанных компонентов и примеры

  • Textfield ( работает )
  • Textarea ( возвращает undefined )
  • Select ( полностью не поддерживается, требуется надстройк )
  • Checkbox, Switch ( работает )
  • Radiogroup ( возвращает не корректное значение on )
  • Slider ( полностью не адаптирован, требуется надстройка )
  • Calender ( полностью не адаптирован, требуется надстройка )
  • Combobox ( полностью не адаптирован, требуется надстройка )
  • Upload ( не корректный onChange, где сначала идет файл, а потом event )

Пример работает на актуальном WEB, под надстройкой подразумевается то, что компонент неспособен работать без передаваемого value и самостоятельно он работать не может
Пример: https://codesandbox.io/p/devbox/react-hook-form-research-forked-64n5ly ( Preview 5173 )

2. Сделать компонент Form по аналогии с AntDesign

Пример: https://ant.design/components/form

Данный способ более прост в реализации. По сути есть 2 компонента Form который объеденяет все поля и компонент FormItem, который является оберткой. В эту обертку мы сможем запихнуть функционал, который позволит работать Select, Calender, Slider, Combobox, которые требуют обязательный параметр value.

Я думаю наилучшим решением будет добавить специальный пропс, который смог бы передавать унифицированные данные в форму и компонент FormItem передавал бы его для получения данных. Например onChangeForForm.

Также сможем в будущем добавить стилизацию для формы

По итогу я предлагаю:

  1. Сделать компонент Form по аналогии с AntDesign с поддержкой всех компонентов
  2. Реализовать поддержку useForm у TextField, TextArea, Checkbox, Switch, Radiogroup, Upload, Select. Основная сложность будет с Select
@iljs iljs self-assigned this Jul 30, 2024
@shuga2704
Copy link
Contributor

Стандартный селект не может работать в форме. Чтобы сделать uncontrolled Select, нужно добавить его нативную версию. Таргет будет выглядеть также, но выпадающий список уже будет нативный. И такой селект уже будет работать в формах.

@iljs
Copy link
Contributor Author

iljs commented Jul 31, 2024

Тогда в первую очередь делать 2 вариант, с разработкой компонента form. А к хуку подключать по возможности

@iljs
Copy link
Contributor Author

iljs commented Aug 27, 2024

Пример использования:

<Form
    styled={...data}
    onChange={onChange}
    onSubmit={onSubmit}
>
    <FormItem<TextFieldType>>
        <Input />
    <FormItem/>
    <FormItem<TextFieldType>>
        <Input />
    <FormItem/>
    <FormItem<ButtonType>>
        <Button />
    <FormItem/>
</Form>

В зависимости от типа FormItem система будет прокидывать onChange и другие нужные свойства в children, также можно будет написать свой обработчик при необходимости, прокинув функцию в FormItem

@TitanKuzmich
Copy link
Contributor

TitanKuzmich commented Sep 2, 2024

Можешь подробнее описать, как будет определяться тип для FormItem. Пока не совсем понятно, как вычленить нужный onChange. И конечные пропсы нужно будет складывать в FromItem или же в его children (Button/TextField)?

@neretin-trike
Copy link
Collaborator

опиши пожалуйста пример с колбэками. Не сильно хочется использовать дженерики в компонентах, т.к. мы наружу страемся теперь не поставлять типы, и пользователям каждый раз нужно будет самим их собирать

@iljs
Copy link
Contributor Author

iljs commented Sep 3, 2024

  1. Пропсы будут прокидываться через FormItem
  2. Если мы не хотим использовать дженерики, можем сделать пропс type, в зависимости от него будет вычленен нужный onChange в нужном типе.

<Form
    styled={...data}
    onChange={onChange}
    onSubmit={onSubmit}
>
    <FormItem type="textField">
        <Input />
    <FormItem/>
    <FormItem type="textField" onBlur={onBlur}>
        <Input />
    <FormItem/>
    <FormItem type="button">
        <Button />
    <FormItem/>
</Form>

@Yeti-or
Copy link
Contributor

Yeti-or commented Sep 3, 2024

Для компонента Form явно нужен дизайн, просто для поддержки useForm нужно поменять компоненты или написать какие-то адаптеры к useForm

давайте второе сделаем сейчас а первое сделаем попозже ?

@TitanKuzmich
Copy link
Contributor

TitanKuzmich commented Sep 3, 2024

Если будем указывать

<FormItem type="textField">
        <Input />
<FormItem/>

зачем тогда Input как children пробрасывать?
Кажется, что в таком подходе у пользователя много шансов прокинуть пропсы не в FormItem. Нужно резолвить конечный input внутри самого Item, как мне кажется

@vadim-kudr
Copy link
Contributor

У части компонентов внутри не хватает обработчика onInput, тут на него бы еще внимание обратил.

В том же TextField, onChange не сразу вызывается. И если от формы требуется моментальная реакция на изменения, то придется компоненты некоторые допилить

@Yakutoc
Copy link
Collaborator

Yakutoc commented Sep 3, 2024

@iljs А есть схема как события тогда должны работать и обрабатываться?

@iljs
Copy link
Contributor Author

iljs commented Sep 5, 2024

На текущий момент поддержка есть у следующих компонентов: TextField, Checkbox, Switch, RadioGroup / RadioGroup

Пример:

import { useForm } from 'react-hook-form';

const { register, handleSubmit, watch } = useForm();
    const onSubmit = (data) => {
        console.log(data);
    };

return (
        <form onSubmit={handleSubmit(onSubmit)}>
            <TextField {...register('textfield')} placeholder="Textfield" />
            <Checkbox {...register('checkbox')} label="Checkbox" />
            <Switch {...register('switch')} label="Switch" labelPosition="after" />
            <RadioGroup aria-labelledby="radiogroup-title-id">
                <div id="radiogroup-title-id" style={{ margin: '1rem 0', fontWeight: '600' }}>
                    Выберите язык программирования для изучения.
                </div>
                {itemsRadiobox.map((item) => (
                    <Radiobox
                        key={item.value}
                        value={item.value}
                        label={item.label}
                        disabled={item.disabled}
                        {...register('radiobox')}
                    />
                ))}
            </RadioGroup>
            <Button type="submit">Отправить</Button>
        </form>
    );
};

В компоненте TextArea не корректно работает ref на 120 строке. Если убрать условия, то компонент совместим

Для Select, Combobox и Slider требуется обертка, вот пример:

const FormCombobox = (props) => {
    const { onChange, onBlur, name, ref, children } = props;
    const [value, setValue] = useState();

    const onChangeValue = (e) => {
        setValue(e);
        onChange({
            target: {
                value: e,
                name,
            },
        });
    };

    const onBlurValue = (e) => {
        onBlur(e);
    };

    return (
        <Combobox {...props} value={value} onChangeValue={onChangeValue} onBlur={onBlurValue} name={name} ref={ref}>
            {children}
        </Combobox>
    );
};

После этого можно использовать вот так:

const DefaultForm = () => {
    const { register, handleSubmit, watch } = useForm();
    const onSubmit = (data) => {
        console.log(data);
    };

    return (
        <form onSubmit={handleSubmit(onSubmit)}>
            <FormComboBox {...register('сombobox')}>
                {getSelectItems('item', 6).map((item) => (
                    <ComboboxItem
                        key={item.value}
                        value={item.value}
                        text={item.child}
                    />
                ))}
            </FormComboBox>
            <Button type="submit">Отправить</Button>
        </form>
    );
};

PR: #1421
Storybook: https://plasma.sberdevices.ru/pr/pr-1421/new-hope-storybook/?path=/docs/plasma-b2c-form--docs

@shuga2704
Copy link
Contributor

Думаю надо попробовать реализовать Select и Combobox без обертки.

@neretin-trike
Copy link
Collaborator

neretin-trike commented Sep 6, 2024

а этот пример показывает, что мы должны будем эти компоненты сделать и отдать пользователям или просто как пример и такие надстройки должны будут сами разработчики делать?

или ждём, когда эту поддержку @shuga2704 добавит?

@iljs
Copy link
Contributor Author

iljs commented Sep 6, 2024

@neretin-trike , мы должны сделать
В задаче сейчас стоит вопрос radiobox, textarea, calender, slider

@vadim-kudr
Copy link
Contributor

vadim-kudr commented Sep 9, 2024

Еще бы добавил возможность прокидывание пропсов формы без обертки FormItem, аля через {...register('myControl', { onChange: ..., onBlur: ... )}

@iljs
Copy link
Contributor Author

iljs commented Sep 10, 2024

Пример использования при изменения компонента:

const DefaultForm = () => {
    const { register, handleSubmit } = useForm();
    const onSubmit = (data) => {
        console.log(data);
    };

    return (
        <form onSubmit={handleSubmit(onSubmit)} style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
            <TextField {...register('textfield')} placeholder="Textfield" required={false} />
            <Slider {...register('slider')} placeholder="Slider" type="single" min={0} max={100} />
            <Button type="submit">Отправить</Button>
        </form>
    );
};

Пример использования с хуком:
Универсальное решение, где в каждом компоненте есть onChangeForm, хук перенаправляет этот event на onChange. Сложность в разработке из-за типов

const DefaultForm = () => {
    const { register, handleSubmit } = useForm();
    const onSubmit = (data) => {
        console.log(data);
    };

    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-expect-error
    const FormSlider = useFormComponent(Slider, register('slider'));

    return (
        <form onSubmit={handleSubmit(onSubmit)} style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
            <TextField {...register('textfield')} placeholder="Textfield" required={false} />
            <FormSlider placeholder="Textfield" min={0} max={100} value={0} />
            <Button type="submit">Отправить</Button>
        </form>
    );
};

Пример использования с оберткой:

Решение похоже на верхнее, но более просто в обслуживании из-за своей простоты.

<form onSubmit={handleSubmit(onSubmit)}>
            <FormSlider {...register('slider')}></FormSlider>
            <Button type="submit">Отправить</Button>
</form>

С использованием Controller ( работает без правок ) :

const DefaultForm = () => {
    const { control, handleSubmit } = useForm({
        defaultValues: {
            textfield: 'John Doe',
            textarea: 'Default description',
            slider: 10,
        },
    });
    const onSubmit = (data) => {
        console.log(data);
    };

    return (
        <form onSubmit={handleSubmit(onSubmit)} style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
            <Controller
                name="textfield"
                control={control}
                render={({ field }) => <TextField {...field} label="TextField" />}
            />
            <Controller
                name="textarea"
                control={control}
                render={({ field }) => <TextArea {...field} label="TextArea" />}
            />
            <Controller
                name="slider"
                control={control}
                render={({ field }) => <Slider {...field} label="Name" min={0} max={100} />}
            />
            <Button type="submit">Отправить</Button>
        </form>
    );
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants