Управление сложным form state в React: паттерны и решения

#react#forms#state-management#frontend

TL;DR

В сложных React-формах с динамическими полями, валидацией и многошаговыми flow локальный state быстро становится неуправляемым. Рассматриваем архитектурные подходы: композиция хуков, контекст, Formik/RHF, конечные автоматы и кастомные решения на основе Immer.

Введение: когда useState уже не enough

Практически каждый senior frontend-разработчик сталкивается с ситуацией, когда первоначально простая форма превращается в монструозный компонент с размазанным по коду состоянием. Conditional rendering, каскадные валидации, динамические field arrays - все это быстро приводит к spaghetti code.

Проблемные симптомы:

Рассмотрим проверенные в production подходах решения.

1. Композиция кастомных хуков

Для medium-complexity форм эффективно разделение логики на специализированные хуки:

function useMultiStepForm(initialData: FormData) {
  const [step, setStep] = useState(0);
  const [data, setData] = useState(initialData);
  
  const next = useCallback(() => {
    setStep(prev => Math.min(prev + 1, STEPS_COUNT));
  }, []);

  // Аналогично для prev(), validateCurrentStep() и т.д.
  return { step, data, next /* ... */ };
}

function useDynamicFields(fieldsConfig) {
  const [fields, setFields] = useState(fieldsConfig);
  
  const addField = useCallback((type) => {
    setFields(prev => [...prev, createField(type)]);
  }, []);

  // Логика conditional fields
  return { fields, addField /* ... */ };
}

Преимущества:

2. Специализированные библиотеки

Для enterprise-решений часто выбирают Formik или React Hook Form (RHF). Рассмотрим RHF как современный стандарт:

const { register, control, handleSubmit, formState } = useForm({
  resolver: yupResolver(schema), // Интеграция с Yup
  defaultValues: async () => await fetchInitialData(),
});

<Controller
  control={control}
  name="dynamicField"
  render={({ field }) => (
    <ConditionalInput 
      {...field}
      show={watch('showCondition')}
    />
  )}
/>

Ключевые фичи RHF:

3. State machines (XState)

Для форм с комплексным flow идеально подходят конечные автоматы:

const formMachine = createMachine({
  id: 'signup',
  initial: 'basicInfo',
  states: {
    basicInfo: {
      on: {
        NEXT: { target: 'payment', cond: 'isBasicValid' }
      }
    },
    payment: {
      on: {
        PREV: 'basicInfo',
        NEXT: 'review'
      }
    }
    // ...
  }
});

function SignupForm() {
  const [state, send] = useMachine(formMachine);
  
  return (
    {state.matches('basicInfo') && <BasicStep onSubmit={() => send('NEXT')} />}
    // ...
  );
}

Преимущества:

4. Immer + Context для кастомных решений

Когда нужен баланс гибкости и контроля:

const FormContext = createContext<FormContextType>(null!);

function FormProvider({ children }) {
  const [state, produceState] = useImmer(initialState);
  
  const updateField = useCallback((path, value) => {
    produceState(draft => {
      set(draft, path, value); // Используем lodash.set или аналоги
    });
  }, []);

  return (
    <FormContext.Provider value={{ state, updateField }}>
      {children}
    </FormContext.Provider>
  );
}

// В компоненте:
const { updateField } = useContext(FormContext);
updateField('user.address.city', 'Moscow');

Плюсы подхода:

Практические рекомендации

  1. Для простых форм: оставайтесь на useState + кастомным хукам
  2. Для CRUD-интерфейсов: React Hook Form + Yup
  3. Для wizard/multi-step: XState или аналоги
  4. Для максимальной гибкости: Immer-based решение

Оптимизация производительности:

Заключение

Выбор архитектуры формы зависит от конкретных требований, но главное - сознательное проектирование state management с учетом масштабируемости. Современные инструменты позволяют поддерживать сложную логику без компромиссов в читаемости кода.

Для legacy-проектов рекомендую incremental migration: начинаем с изоляции сложной логики в хуки, затем поэтапно внедряем специализированные решения для проблемных участков.


Источник: https://www.reddit.com/r/reactjs/comments/1szlrzp/how_are_you_handling_complex_form_state_in_react/