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

Is this the right way ? #24

Closed
adelin-b opened this issue Jul 21, 2018 · 9 comments
Closed

Is this the right way ? #24

adelin-b opened this issue Jul 21, 2018 · 9 comments

Comments

@adelin-b
Copy link

Hello, I am trying to implement custom components for material ui and redux form.

Here is a sample where I was wondering if you could see if something from redux-form-gen was wrongly used.
I commented the parts where I have questions but if you see something that could have been done cleaner I would be really enthusiast to hear it.
https://codesandbox.io/s/yj6x97x99v

@bmv437
Copy link
Contributor

bmv437 commented Jul 21, 2018

Hey @adberard!

Thanks for your questions and interest. I took a look at your codesandbox and the questions in your comments, and compiled a list of answers below.

Here a an updated version of your codesandbox to follow along:
https://codesandbox.io/s/4qy8v0ony9


QUESTION:
I had to comment injectGenProps in order to use my own validate function,
but then the required key in the field objects became useless,
how can I still use the required key instead of writing which components
are required in my validate.js:3

It looks like the reason that you wanted to use a custom validate function was because you wanted to use a regex to validate the email, right? If that is the case, there is good news, because redux-form-gen supports custom validation by using our conditional operators. I just noticed that I forgot to add the conditionalValid field property to the docs, but I've taken a note to add it. We have a regex operator that will compare the value against the regex during validation. It would look something like this:

{
  type: "text",
  formType: "email",
  questionId: "email",
  label: "Email",
  required: true,
  conditionalValid: {
    regex: "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}$"
  }
}

Within the customFieldTypes.js for the password field type, you asked:

// QUESTION is there other components that I should nullify ?

Here is a link to the source for genericFieldProps()

It essentially boils down to:

export const genericFieldProps = ({field}) => ({
  _genFieldComponent: Field,
  _genLabelComponent: GenericRequiredLabel,
  name: field.questionId
});

Because the TextField for material-ui has the input, label, and required indicator built in, you wouldn't need the _genLabelComponent. There shouldn't be anything else you need to nullify.


QUESTION:
How would I wrap childFields in a FormSection from react redux ?

You can extend the section field type to render a <FieldSection /> component from redux-form, but after trying to implement an example for you I think it may be a little more detailed that I originally thought. Let me whip something up for that and answer it in issue #23.


Thanks you so much for your questions, and let me know if you have any feedback as well. I have lots of ideas for features and improvements to the field type API, and look forward to hearing any suggestions you may have.

@adelin-b
Copy link
Author

adelin-b commented Jul 22, 2018

Im the one thanking you so much for your really quick and detailed answers !
But If you like questions I may still have some that rised up.

How can I set my own custom message for precise errors in field ? I need this because im planning on doing some multi-languages.

How can I put my own function into validate (which would my global state for coherent dates for examples)

I really look forward #23, I tryed to play around but Im not getting everything about how childrens are generated with _gencomponent !

@adelin-b
Copy link
Author

I played a bit the SectionField that you started to write and it seem that validation with nested component like this isnt going to work. Even when using my own validate.js its not very simple, ive read that redux form v8 will enhance validation

@bmv437
Copy link
Contributor

bmv437 commented Jul 23, 2018

How can I set my own custom message for precise errors in field ? I need this because im planning on doing some multi-languages.

You can use the requiredMessage and invalidMessage to overwrite the message used for that field. here's an example:

{
  type: 'text',
  questionId: 'Name',
  conditionalValid: {
    minLength: 2
  },
  invalidMessage: 'Must be at least two characters'
}

If you're planning on using a multi-language framework, such as react-intl, you could make your invalidMessage the message id for your defined messages in react-intl instead of an actual error message. Then you would expect the meta.error that comes down from redux-form to be a message id and would run formatMessage(formMessages[error]).

something like this:

// the field
{
  type: 'text',
  questionId: 'Name',
  conditionalValid: {
    minLength: 2
  },
  invalidMessage: 'minTwoChars' // the message id from defineMessages
}

// messages
import {defineMessages} from 'react-intl';
const formMessages = defineMessages({
  minTwoChars: {
    id: 'formMessages.minTwoChars',
    description: 'Form error message for min two characters',
    defaultMessage: 'Must be at least two characters'
  }
});


// field component
import {injectIntl} from 'react-intl';
const TextField = injectIntl(({intl: {formatMessage}, meta, ...props}) => (
  <Input
    {...props.input}
    error={meta.touched && meta.invalid && formatMessage(formMessages[meta.error])}
  />
));

How can I put my own function into validate (which would my global state for coherent dates for examples)

Not sure what you mean exactly. If you want to provide a custom validate function in addition to having injectGenProps do validation, you can pass it like this:

const MyForm = injectGenProps(
  reduxForm({
    form: 'exampleForm',
    onSubmit
    // don't pass validate here, since it will get overridden by injectGenProps. you need to pass it above injectGenProps
  })(MyFields)
);

const customValidate = (formValues) => {
  // do some custom validation here, and return an errors object
};

const App = () => (
  <Provider store={store}>
    {/* pass a custom validate function here: */}
    <MyForm fields={fields} customFieldTypes={customFieldTypes} validate={customValidate} />
  </Provider>
);

This is more of a last resort if you can't build the logic you need with a conditional object. There is also the option to provide your own customOperators for use in conditional object. You can find examples of conditionalOperators here:
https://github.com/isobar-us/redux-form-gen/blob/master/src/conditionalUtils.js#L39


I really look forward #23, I tryed to play around but Im not getting everything about how childrens are generated with _gencomponent !

Let me see if I can explain:

The <FormGenerator /> runs through the list of fields and recursively renders <GenField /> components for each field. GenField handles the rendering of childFields internally so that each field component implementation doesn't have to be concerned about it. Also of note, childFields are not actually rendered as children react nodes, but as siblings to the field. The parent/child relationship was more referring to the visibly/hidden relationship that parents and children have. If a parent field is hidden, so are it's children.

If you do want to render the childFields inside of your _genComponent (as is the case for a FormSection, There is an override to have GenField skip rendering of childFields by passing _genSkipChildren: true in the field type definition. Then in your _genComponent you can read the field.childFields from props, and map over them to render <GenField field={field} /> for each field.


I played a bit the SectionField that you started to write and it seem that validation with nested component like this isnt going to work. Even when using my own validate.js its not very simple, ive read that redux form v8 will enhance validation

After digging around a bit, I realized that the issues we're seeing around the FormSection and validation stem from bugs in the way the pathPrefix is handled in the validation of conditional objects. There's a larger discussion to be had in how to fix this, and I have some ideas I've been working on.

In the meantime, I realized you can work around it by prefixing your questionId's individually, instead of using a FormSection + questionId at the section level.

{
  type: 'section',
  label: 'Step 1',
  childFields: [
    {
      type: 'text',
      inputType: 'email',
      // questionId's have been prefixed with "parent."
      questionId: 'parent.email',
      label: 'Email',
      required: true,
      conditionalValid: {
        regex: '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}$'
      }
    },
    {
      type: 'text',
      inputType: 'text',
      questionId: 'parent.firstName',
      label: 'First Name',
      required: true
    },
    {
      type: 'text',
      inputType: 'text',
      questionId: 'parent.secondName',
      label: 'Second Name'
    },
    {
      type: 'password',
      questionId: 'parent.password',
      inputType: 'password',
      label: 'Password',
      required: true
    }
  ]
}

@adelin-b
Copy link
Author

adelin-b commented Jul 23, 2018

Thank you again for your super detailled explanations !
I didnt saw customValidation examples so I didnt knew how to use it. I was more thinking about putting the function in the field object directly.

   {
      type: 'password',
      questionId: 'parent.password',
      inputType: 'password',
      label: 'Password',
      required: true,
      validate: (value) => {
                             ...  
       }
    }

I figured out how to use the childrenFields finally and I kept on adding components
https://codesandbox.io/s/j4n568lo5
I created a stepper field. But im now stuck at creating checkboxes as you can test the input value isnt the correct one and I fail to understand why. (CheckboxField.js)

@bmv437
Copy link
Contributor

bmv437 commented Jul 23, 2018

Wow, that stepper field type is impressive! Really creative way to implement a stepped wizard.


I didnt saw customValidation examples so I didnt knew how to use it. I was more thinking about putting the function in the field object directly.

Ah, I see what you mean. We've stuck to a JSON-compatible field structure in order to make sure the field structure could be requested from a back-end, since you can't describe a function in JSON. That's also the reasoning behind the conditional object API, for things like conditionalVisible, conditionalValid, conditionalDisabled, and conditionalRequired, as well as the customOperators API so you can define your own operators for custom logic like date comparisons for example.

From what I can tell, all the logic that you have in your validate function can be described as a conditionalValid property, with custom messages like I described above. For example:
requiredMessage: 'Champs requis'
invalidMessage: 'Addresse Email invalide'


But im now stuck at creating checkboxes as you can test the input value isnt the correct one and I fail to understand why. (CheckboxField.js)

Are you trying to create a single checkbox field? or more like a checkbox list, where you can select multiple options?

Checkbox Lists can work like a list. the value of the checkboxList can be an array, where all the values of the selected options will appear in the array. you can use a custom onClick on the rendered checkbox input to add/remove values from the array. Since you always want the value to be an array, you can use _genDefaultValue: [], so the generator will populate it be default.

const checkboxListType = ({ field, ...options }) => ({
  ...genericFieldProps({ field, ...options }),
  _genDefaultValue: [],
  component: CheckboxListField,
  options: field.options
});

Single Checkboxes can be tricky in forms. Since it can only have two states, true or false. If it comes in as null/undefined/"" and gets checked (set to true), and then unchecked (set to false), it breaks the dirty/pristine state of redux-form. For this case, we have a concept of _genDefaultValue which will ensure that default values are filled when the form first boots up. For a checkbox, the default value can be set to false, and we provide a custom _genIsFilled to tell the generator that this field is filled out when the value === true. Try this out:

const checkboxType = ({ field, ...options }) => ({
  ...genericFieldProps({ field, ...options }),
  _genDefaultValue: false,
  _genIsFilled: ({ data, field }) => _.get(data, field.questionId) === true,
  component: CheckboxField
});

As a note, if you don't use injectGenProps, you won't get default values from _genDefaultValue since that happens inside there and is passed to the form as initialValues.

@adelin-b
Copy link
Author

adelin-b commented Jul 24, 2018

Thanx again for explanation, but still ended up pulling my hair off on the checkbox list.

So I tryed to use _genDefaultValue, but once inside the component I cant find how to bind the list of checkbox to the value of the form. I need to use objects instead of array (because their key->value never change when you remove or insert an element).

Can you help me making a basic working checkbox list component ?
something like

{
        type: "checkbox",
        questionId: "DevModel",
        options: [
          { label: "Up", value: "1" },
          { label: "Down", value: "2" },
          { label: "Middle", value: "3" }
        ]
},

Would submit a value like :

 DevModel: {
          up: true,
          down: false,
          middle: true
        }

Also with an onclick event that only modify values I dont think that box would appear checked when I will need to inject initialvalue in reduxForm(MyForm) in the futur.... I really need to get how you change the input. Im looking at the radio component (https://github.com/isobar-us/redux-form-gen/blob/master/src/defaultFieldTypes/components/RadioField.js) too see how you implemented having bunch of inputs in one 1 group but didnt managed to get something working.

Here is my latest attempt
https://codesandbox.io/s/j4n568lo5?module=%2Fsrc%2FCheckboxField.js

@bmv437
Copy link
Contributor

bmv437 commented Jul 24, 2018

Here's a working example with the CheckboxField.
https://codesandbox.io/s/wy23z9lv9w?module=%2Fsrc%2FCheckboxField.js

A quick list of some changes:

  • CheckboxField.js
    • modified props to <Checkbox /> to include checked
    • updated logic in custom this.onChange handler
  • customFieldTypes.js
    • modified the _genDefaultValue for the checkbox type to build the value object you were looking for instead of an array
  • index.js
    • wrapped reduxForm in injectGenProps HOC
    • moved the custom validate function to be a prop of <MyForm />.
    • added customFieldTypes as a prop of <MyForm />. injectGenProps needs this in order to pickup your _genDefaultValue and inject the proper initialValues into redux form.

@adelin-b
Copy link
Author

Your help is precious, I was far to get the logic of it. I will keep on adding materials components :)

@adelin-b adelin-b closed this as completed Nov 8, 2019
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

2 participants