Redux Form - how to disable sync/async validation with a flag - reactjs

I am looking for the most non invasive way to disable validation of redux form for dev/debug purposes
I have a wizard form similar to the tutorial https://redux-form.com/8.1.0/examples/wizard/ but I am using field validators plus an async validator
I would still like to display errors but be able to proceed to the next wizard step even when validation fails
I would like to add a flag to the url like ?ignore-validation=true (using react router)
here is a fragment of my wizard step1:
export class CustomerStep1 extends React.PureComponent<FormProps> {
render(): React.Node {
const {
handleSubmit,
} = this.props;
const submit = handleSubmit(createValidateCardAction);
return (
<form onSubmit={submit}>
<Field
name="card_number"
placeholder="7777 1234 5678 9012"
component={Input}
validate={cardNumber}
/>
...
export default reduxForm({
form: 'customerWizard',
destroyOnUnmount: false,
forceUnregisterOnUnmount: true,
})(CustomerStep1);

I see two possibilities here:
1) Split your wizard forms in more than one form and validate them separately. You could create another state to control which form you want to show. Even though it has more states to manage, I would pick that one.
2) You could leave just the async validation for each field and throw the errors there. Also, You must create a state to manage the page and manage it inside the async validation functions. I create an example of this second approach: https://codesandbox.io/s/o46k8lx7kz

Related

How should I show editable data after it has been submitted with a form so that I can reuse a form components?

If I was making a website where the user submits some data using a form and then the user is redirected to a page with a unique URL where they can view this data, what is the best way to display this data to user in a way in which they can make updates to it?
To give some context I am using react and right now I am showing the same form again on the view data page, but as the forms have slightly different display options, such as some fields being disabled and some extra buttons I am repeating the same form in two seperate components which seems to go against the react way of doing things.
The way I would personally do it is have all your form elements in a single component and use it for the create and edit page, then just pass a "isCreating" or "isEditing" prop, or something similar. In that component, you can then conditionally disable your inputs.
Component
const SomeForm = ({ isEditing }) => {
const onSubmit = () => {
if (isEditing) {
// Make request to update endpoint
} else {
// Make request to create endpoint
}
};
return (
<>
<form onSubmit={onSubmit}>
<input disabled={isEditing} />
<input />
</form>
</>
);
};
Create Page Usage
<MyForm />
Edit Page Usage
<MyForm isEditing />
Note that if you're doing a ton of conditional logic/rendering based on whether you're creating or editing a form, it might be better to split it into two separate form components. If you're simply disabling some inputs on the edit page and doing different submit logic, then I think this is fine.

Manage multiple redux form on page

I have user data coming from back-end as an array of objects,
[{firstname: 'John', lastname: 'Doe'}, {firstname: 'Jane', lastname: 'Doe'}]
I am using a redux-form component to render these data.
tried to make the object keys dynamic by binding index to it, but then got unsure how to handle validation, data population etc.
Working code sample can be seen at,
https://codesandbox.io/s/determined-lake-ln25c
Now changing a field in on 1 form affects the other forms state. like after entering value in one field, all 'clear values' buttons get enabled.
The problem is that all your forms are actually assigned the same name (form property value) when you call reduxForm. This property is treated as unique form identifier so that data for specific form can be get from store. According to docs:
form: string [required]
the name of your form and the key to where your form's state will be
mounted under the redux-form reducer
Please note that although you generate unique formNumber using Math.random you're doing it only once in module file (please use console.log to see that formNumber is generated only once). So all instances of the UserForm component are assigned the same name at 'design time'. As a result when you then import form component and render it 3 times all instance use the same name. You can say that from redux-form point of view all these 3 form instances are actually the same single form and they use the same data from store.
According to redux-form docs all reduxForm config options
may be passed into reduxForm() at "design time" or passed in as props to your component at runtime.
So solution is to define unique form as a component prop at 'runtime' instead of in reduxForm config:
function App() {
return (
<div className="container">
<h1 className="mb-3">Users</h1>
<hr />
{userData.map((user, idx) => {
return <UserForm form={`form-${idx}`} key={`user_${idx}`} data={user} idx={idx} />;
})}
</div>
);
}
And of course remove this value from reduxForm config:
export default reduxForm({
validate
})(UserForm);
enter code here
This way all form instances are independent.
Since now all form instances are independent of each other you don't have to define field names this way:
name={`firstName_${idx}`}.
You can do it like this:
name='firstName'

How to submit #atlaskit/form remotely

I want to submit a #atlaskit/form from the button outside the form. I have gone through https://atlaskit.atlassian.com/packages/core/form but no documentation regarding this
Warning: When trying to submit a form remotely, you would need to go out of your way to actually make the validation work. This is applicable to HTML forms, thus not limited by Atlaskit forms.
Read about it here:
How to force a html5 form validation without submitting it via jQuery
https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Form_validation
Answer:
Atlaskit form actually renders the native html form underneath. Therefore,
we can attach a ref to the Form element and then trigger submit of the form property of the current ref.
Example:
// attach the ref to form
class extends React.Component{
form = React.createRef();
render() {
<Form
ref={this.form}
{...props}
>
{children}
</Form>
}
}
Trigger submit on html form:
this.form.current.form.submit()
See example codesandox here.

Issue with combining multiple forms into one, using react and redux-form

In my app i have multiple forms that handle a specific usecase:
- adding pointers
- adding ssl certificates
- ...
These forms are used stand-alone, but sometimes there's the need to combine two or more forms into one.
I've read the wizard example (https://redux-form.com/7.1.2/examples/wizard/) and have applied it to my code. It works succesfully when used as a wizard, when only one form is visible at a time.
But when both forms need to be visible at the same time, i run into problems. When entering data into the 2nd form, the redux-form uses the 1st form for validation.
I hope i can explain it well enough. Here some pseudo code:
<Wizard>
<WizardStep
title={formatMessage(messages.step1Title)}
isActive
step={1}
>
<DomainPointerFormContainer
initialValues={{
pointer: pointer && pointer.pointer,
domain: application.domain
}}
/>
</WizardStep>
<WizardStep
title={formatMessage(messages.step2Title)}
isActive
step={2}
>
<SslCertificateFormContainer
existingCertificates={existingCertificates}
onSubmit={submitForm}
submitLabel={formatMessage(messages.submitLabel)}
submittingLabel={formatMessage(messages.submittingLabel)}
/>
</WizardStep>
</Wizard>
DomainPointerFormContainer looks like this:
import DomainPointerForm from 'Form/DomainPointerForm';
...
export default reduxForm({
form: 'wizard',
destroyOnUnmount: false,
forceUnregisterOnUnmount: true,
enableReinitialize: true,
})(DomainPointerForm);
SslCertificateFormContainer looks like this:
import SslCertificateForm, { validate } from 'Form/SslCertificateForm';
...
export default reduxForm({
form: 'wizard',
destroyOnUnmount: false,
forceUnregisterOnUnmount: true,
enableReinitialize: true,
validate
})(SslCertificateForm);
The validate function looks a lot like in the redux-form examples, so nothing special there.
Is there a way to make this work, or do i have to create a new form that combines the contents of the two separate forms, which leads to a lot of duplication...

I am using Redux. Should I manage controlled input state in the Redux store or use setState at the component level?

I have been trying to figure out the best way to manage my react forms. I have tried to use the onChange to fire an action and update my redux store with my form data. I have also tried creating local state and when my form gets submitted I trigger and action and update the redux store.
How should i manage my controlled input state?
I like this answer from one of the Redux co-authors:
https://github.com/reactjs/redux/issues/1287
Use React for ephemeral state that doesn't matter to the app globally
and doesn't mutate in complex ways. For example, a toggle in some UI
element, a form input state. Use Redux for state that matters globally
or is mutated in complex ways. For example, cached users, or a post
draft.
Sometimes you'll want to move from Redux state to React state (when
storing something in Redux gets awkward) or the other way around (when
more components need to have access to some state that used to be
local).
The rule of thumb is: do whatever is less awkward.
That is, if you're sure that your form won't affect global state or need to be kept after your component is unmounted, then keep in the react state.
You can use the component's own state. And then take that state and give it as an argument to the action. That's pretty much the "React way" as described in the React Docs.
You can also check out Redux Form. It does basically what you described and links the form inputs with Redux State.
The first way basically implies that you're doing everything manually - maximum control and maximum boilerplate. The second way means that you're letting the higher order component do all the work for you. And then there is everything in between. There are multiple packages that I've seen that simplify a specific aspect of form management:
React Forms -
It provides a bunch of helper components to make form rendering and validation more simple.
React JSON schema -
Allows one to build an HTML form from a JSON schema.
Formsy React -
As the description says: "This extension to React JS aims to be that "sweet spot" between flexibility and reusability."
Update: seems these days Redux Form is being replaced with:
React Final Form
And one more important contender in the space that's worth checking out is:
Formik
Tried out React Hook Form in my last project - very simple, small footprint and just works:
React Hook Form
TL;DR
It's fine to use whatever as it seems fit to your app (Source: Redux docs)
Some common rules of thumb for determing what kind of data should be
put into Redux:
Do other parts of the application care about this data?
Do you need to be able to create further derived data based on this original data?
Is the same data being used to drive multiple components?
Is there value to you in being able to restore this state to a given point in time (ie, time travel debugging)?
Do you want to cache the data (ie, use what's in state if it's already there instead of re-requesting it)?
These questions can easily help you identify the approach that would be a better fit for your app. Here are my views and approaches I use in my apps (for forms):
Local state
Useful when my form has no relation to other components of the UI. Just capture data from input(s) and submits. I use this most of the time for simple forms.
I don't see much use case in time-travel debugging the input flow of my form (unless some other UI component is dependent on this).
Redux state
Useful when the form has to update some other UI component in my app (much like two-way binding).
I use this when my form input(s) causes some other components to render depending on what is being input by the user.
Personally, I highly recommend keeping everything in the Redux state and going away from local component state. This is essentially because if you start looking at ui as a function of state you can do complete browserless testing and you can take advantage of keeping a reference of full state history (as in, what was in their inputs, what dialogs were open, etc, when a bug hit - not what was their state from the beginning of time) for the user for debugging purposes. Related tweet from the realm of clojure
edited to add: this is where we and our sister company are moving in terms of our production applications and how we handle redux/state/ui
Using the helper libraries is just more quick and avoid us all the boilerplate. They may be optimized, feature rich ...etc. As they make all different aspects more of a breeze. Testing them and making your arsenal as knowing what's useful and better for the different needs, is just the thing to do.
But if you already implemented everything yourself. Going the controlled way. And for a reason you need redux. In one of my projects. I needed to maintain the form states. So if i go to another page and come back it will stay in the same state. You only need redux, if it's a mean for communicating the change to multiple components. Or if it's a mean to store the state, that you need to restore.
You need redux, if the state need to be global. Otherwise you don't need it. For that matter you can check this great article here for a deep dive.
One of the problems that you may encounter! When using the controlled inputs. Is that you may just dispatch the changements at every keystroke. And your form will just start freezing. It became a snail.
You should never directly dispatch and use the redux flux, at every changement.
What you can do, is to have the inputs state stored on the component local state. And update using setState(). Once the state change, you set a timer with a delay. It get canceled at every keystroke. the last keystroke will be followed by the dispatching action after the specified delay. (a good delay may be 500ms).
(Know that setState, by default handle the multiple successive keystroke effectively. Otherwise we would have used the same technique as mentioned above (as we do in vanilla js) [but here we will rely on setState])
Here an example bellow:
onInputsChange(change, evt) {
const { onInputsChange, roomId } = this.props;
this.setState({
...this.state,
inputs: {
...this.state.inputs,
...change
}
}, () => {
// here how you implement the delay timer
clearTimeout(this.onInputsChangeTimeoutHandler); // we clear at ever keystroke
// this handler is declared in the constructor
this.onInputsChangeTimeoutHandler = setTimeout(() => {
// this will be executed only after the last keystroke (following the delay)
if (typeof onInputsChange === "function")
onInputsChange(this.state.inputs, roomId, evt);
}, 500);
})
}
You can use the anti-pattern for initializing the component using the props as follow:
constructor(props) {
super(props);
const {
name,
description
} = this.props;
this.state = {
inputs: {
name,
description
}
}
In the constructor or in the componentDidMount hook like bellow:
componentDidMount () {
const {
name,
description
} = this.props;
this.setState({
...this.state,
inputs: {
name,
description
}
});
}
The later allow us to restore the state from the store, at every component mounting.
Also if you need to change the form from a parent component, you can expose a function to that parent. By setting for setInputs() method that is binded. And in the construction, you execute the props (that is a getter method) getSetInputs(). (A useful case is when you want to reset the forms at some conditions or states).
constructor(props) {
super(props);
const {
getSetInputs
} = this.props;
// .....
if (typeof getSetInputs === 'function') getSetInputs(this.setInputs);
}
To understand better what i did above, here how i'm updating the inputs:
// inputs change handlers
onNameChange(evt) {
const { value } = evt.target;
this.onInputsChange(
{
name: value
},
evt
);
}
onDescriptionChange(evt) {
const { value } = evt.target;
this.onInputsChange(
{
description: value
},
evt
);
}
/**
* change = {
* name: value
* }
*/
onInputsChange(change, evt) {
const { onInputsChange, roomId } = this.props;
this.setState({
...this.state,
inputs: {
...this.state.inputs,
...change
}
}, () => {
clearTimeout(this.onInputsChangeTimeoutHandler);
this.onInputsChangeTimeoutHandler = setTimeout(() => {
if (typeof onInputsChange === "function")
onInputsChange(change, roomId, evt);
}, 500);
})
}
and here is my form:
const {
name='',
description=''
} = this.state.inputs;
// ....
<Form className="form">
<Row form>
<Col md={6}>
<FormGroup>
<Label>{t("Name")}</Label>
<Input
type="text"
value={name}
disabled={state === "view"}
onChange={this.onNameChange}
/>
{state !== "view" && (
<Fragment>
<FormFeedback
invalid={
errors.name
? "true"
: "false"
}
>
{errors.name !== true
? errors.name
: t(
"You need to enter a no existing name"
)}
</FormFeedback>
<FormText>
{t(
"Enter a unique name"
)}
</FormText>
</Fragment>
)}
</FormGroup>
</Col>
{/* <Col md={6}>
<div className="image">Image go here (one day)</div>
</Col> */}
</Row>
<FormGroup>
<Label>{t("Description")}</Label>
<Input
type="textarea"
value={description}
disabled={state === "view"}
onChange={this.onDescriptionChange}
/>
{state !== "view" && (
<FormFeedback
invalid={
errors.description
? "true"
: "false"
}
>
{errors.description}
</FormFeedback>
)}
</FormGroup>
</Form>

Resources