2

React can pass onChange functions from functional parent to functional child for the child to use to update the parent. But writing the same onChange functions in several Form building components seems redundant - and I'm hoping there's a better way to build forms faster or a more global method of a child calling parent functions WITHOUT the use of classes.

  1. I've attempting to create an external function and importing it on all components that build forms and pass on onChange to a Field component. But after importing them, they do not have automatically have access to the setValue part of the statehook

  2. I tried Context, but you can't send a dynamic, initial state that has all of the field / values for your form so you get an error regarding controlled and uncontrolled inputs

function Episode({ match }) {

   const [formData, setData] = useState(content.form);

   ...retrieve data from API …

   const updateFieldState = e => {
    switch(e.target.type){
     case 'text':
     case 'textarea':
     case 'select-one':
      setValues({
        ...formValues,
        [e.target.id]: e.target.value
      });
      break;
     case "checkbox":
     . . . . . .(you get the idea here)
    }

   return {
   <Form>
     <Field id="item_title" changeHandler={updateFieldState}/>
     <Field id="item_subtitle" changeHandler={updateFieldState}/>
     . . . . . .(custom child component Field that creates an input and adds updateFieldState to its onClick event )
   <Form>
  }
}

As it stands, I would not only need to redundantly add this changeHandler to every field on this form, but I'd have to redundantly rewrite updateFieldState on EVERY component that builds a from like this creating.

Any help, suggestions or tutorials on how to build faster forms without the use of classes would be appreciated.

3 Answers3

1

Have you considered writing a custom hook?

You could have something like

function useFormHandler(initialData) {
  const [formData, setData] = useState(initialData);
  const fieldChangeHandler = e => {
    // actual implementation that uses setData
  }

  return [formData, fieldChangeHandler];
}

function Episode({ match }) {
  const [formData, fieldChangeHandler] = useFormHandler(content.form);
  ...
}

If you want to avoid passing fieldChangeHandler to each field manually, you could pass it to the <Form> component, and rewrite its render method so that it actually forwards the fieldChangeHandler property to its children (or use context).

Pierre V.
  • 1,625
  • 1
  • 11
  • 14
0

I think you are looking for something binding. U can use either bind or curry method. A simple solution could be based on the below answer/example.

const method = (salute, name) => `${salute} ${name}` 
const Mr = method.bind(null, "Mr.")
const Ms = method.bind(null, "Ms.")
Mr("Deepak")//"Mr. Deepak"
Ms("Rina")//"Ms. Rina"

So your case

onChnage.bind(null, "text")
onChnage.bind(null, "textarea")
xdeepakv
  • 7,835
  • 2
  • 22
  • 32
0

In my opinion, you should create HOC, that consist your updateFieldState logic.

// withFieldFunctions.js
const withFieldFunctions = (WrappedComponent) => {
  const updateFieldState = e => {
    switch(e.target.type){
     case 'text':
     case 'textarea':
     case 'select-one':
      setValues({
        ...formValues,
        [e.target.id]: e.target.value
      });
      break;
     case "checkbox":
     . . . . . .(you get the idea here)
    }
  }

  return <WrappedComponent updateFieldState={updateFieldState} {...this.props} />
}

// SampleFormComponent.js
import withFieldFunction from './withFieldFunctions.js'

const SampleFormComponent = (props) => {
  return (
    <Form>
     <Field id="item_title" changeHandler={props.updateFieldState}/>
     <Field id="item_subtitle" changeHandler={props.updateFieldState}/>
    <Form>
  )
}

return withFieldFunctions(SampleFormComponent)

In that way, you can create a different form and wrap it with withFieldFunctions

Piyush Dhamecha
  • 1,485
  • 1
  • 13
  • 22