Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Abstracting React Hooks for dynamic form fields

I am using the react-hook-form library to create a multi-step dynamic large form (over 70 fields). Most of my form fields are dynamic (user can add/delete more fields by clicking + button). In order to achieve this, I use react-hooks and add a hook for each dynamic field. But I feel like my code is not abstract enough and there might be a smarter way to do this. Below there is an example screenshot of my form:

enter image description here

Respective code for the hooks and form fields are here:

         // hooks
         const [sizes, setSizes] = React.useState([0]);
         const [formats, setFormats] = React.useState([0]);

         const [counter, setCounter] = React.useState(1);

         // buttons
         const addSizesFieldset = () => {
         setSizes(prevIndexes => [...prevIndexes, counter]);
         setCounter(prevCounter => prevCounter + 1);
         };

         const removeSizesFieldset = index => () => {
         setSizes(prevIndexes => [
         ...prevIndexes.filter(item => item !== index)
         ]);
         setCounter(prevCounter => prevCounter - 1);
         };


         const addFormatsFieldset = () => {
         setFormats(prevIndexes => [...prevIndexes, counter]);
         setCounter(prevCounter => prevCounter + 1);
         };

         const removeFormatsFieldset = index => () => {
         setFormats(prevIndexes => [
         ...prevIndexes.filter(item => item !== index)
         ]);
         setCounter(prevCounter => prevCounter - 1);
         };


        // form field arrays
        {sizes.map(index => {
            const fieldName = `sizes[${index}]`;
            return (
                <fieldset name={fieldName} key={fieldName} >
                    <h4>Sizes:</h4>
                    <label>
                        Size:
                        <input
                            type="text"
                            placeholder="Input"
                            name={`${fieldName}.size`}
                            ref={register}

                        />
                    </label>

                    <button type="button" onClick={addSizesFieldset}>
                        +
                    </button>
                    <button type="button" onClick={removeSizesFieldset(index)}>
                        -
                    </button>
                </fieldset>
            );
        })}
            {formats.map(index => {
                const fieldName = `formats[${index}]`;
                return (
                    <fieldset name={fieldName} key={fieldName} >
                        <h4>Formats:</h4>
                        <label>
                            Format:
                            <input
                                type="text"
                                placeholder="Input"
                                name={`${fieldName}.format`}
                                ref={register}

                            />
                        </label>

                        <button type="button" onClick={addFormatsFieldset}>
                            +
                        </button>
                        <button type="button" onClick={removeFormatsFieldset(index)}>
                            -
                        </button>
                    </fieldset>
                );
            })} ´´

Imagine you are doing these hook instants and const add/remove...Fieldset functions for seventy fields. It seemed too much duplication to me but I couldn't find any solution or a new idea other than "React can handle multiple hooks so no worries" comments.

If you want to try your ideas on codesandbox, I prepared below code as well:

https://codesandbox.io/s/react-hook-form-wizard-form-yzno5

like image 897
osmancakirio Avatar asked Aug 22 '26 00:08

osmancakirio


1 Answers

Maybe if you use useReducer, it would be better if you have multiple states

Exemple

const initialState = {
  counter: 1,
  sizes: [0],
  formats: [0],
};

function reducer(state, action) {
  switch (action.type) {
    case 'add':
      return {
        ...state,
        counter: state.counter + 1,
        [action.typeObject]: [...state.sizes, state.counter + 1]
      };
    case 'remove':
      return {
        ...state,
        counter: state.counter - 1,
        [action.typeObject]: [...state.sizes.filter(item => item !== action.index)]
      };
    default:
      return state;
  }
}

/***/

const [stateReducer, dispatch] = React.useReducer(reducer, initialState);

/***/

const addSizesFieldset = () => {
  dispatch({ type: 'add', typeObject: 'sizes' })
};

const removeSizesFieldset = index => () => {
  dispatch({ type: 'remove', typeObject: 'sizes', index })
};

const addFormatsFieldset = () => {
 dispatch({ type: 'add', typeObject: 'formats' })
};

const removeFormatsFieldset = index => () => {
  dispatch({ type: 'remove', typeObject: 'formats', index })
};

Test it

Here is my simple implementation by forking your code: https://codesandbox.io/s/react-hook-form-wizard-form-1h4yq


Edit:

You can even remove 2 functions by adding "typeObject" in param:

const addFieldset = field => () => {
  dispatch({ type: 'add', typeObject: field })
};

const removeFieldset = (index, field) => () => {
  dispatch({ type: 'remove', typeObject: field, index })
};

like image 129
Olivier Nguyen Avatar answered Aug 24 '26 06:08

Olivier Nguyen