redux-form dynamic wizard - reactjs

I'm trying to create a wizard (form) with a dynamic number of steps (with redux-form).
At every step, the user can either go to the last step of the wizard or add an extra step.
An example would be a wizard to order a customizable item. Each step of the wizard contains all the fields to configure the item. The user can then either add another item and configure it or go to the checkout (last step of the wizard).
I'm following the Wizard example but I'm unsure how to add steps dynamically. I tried mixing it up with a FieldArray without success.
Any help?

I did something similar for my work. For your case, if a user selects a certain answer on the same page, you can just do conditional rendering for showing the next page/dynamic values.
My case also had conditional rendering. I had to use formValueSelector API to access form values on different pages. Here is the modified wizard code from the redux-form example Wizard. If you choose male on second page, you get favorite color dropdown question on third page. If you choose female, you get an employed checkbox question.
I made a conditional variable of isMale and return different Field depending on if male answer is true or not. I'm sure there are more elegant ways to do this, but this is one way that worked for me.
https://codesandbox.io/s/34jp9rqp71
I just modified/added these parts of the code.
WizardFormThirdPage.js
import { Field, reduxForm, formValueSelector } from "redux-form";
import { connect } from "react-redux";
let WizardFormThirdPage = props => {
const { sex, handleSubmit, pristine, previousPage, submitting } = props;
var isMale = sex == "male" ? true : false;
return (
<form onSubmit={handleSubmit}>
{isMale && (
<div>
<label>Favorite Color</label>
<Field name="favoriteColor" component={renderColorSelector} />
</div>
)}
{!isMale && (
<div>
<label htmlFor="employed">Employed</label>
<div>
<Field
name="employed"
id="employed"
component="input"
type="checkbox"
/>
</div>
</div>
)}
. . .
</form>
);
};
const mapStateToProps = state => ({
sex: formValueSelector("wizard")(state, "sex")
});
WizardFormThirdPage = connect(mapStateToProps)(WizardFormThirdPage);

Related

Can't get formik validation to work with react-router

Good morning,
I want a react-router link to forward ONLY when the form has no errors, unfortunately the code that's below does forward before the form is even validated so no error shows up and so on. Can you please guide me on how to fix this, so that the form is validated and if it has no errors, the link forwards further?
Here's my code:
<Link to={this.props.formikProps.isValid?'mainMenu': 'itemDetail'}>{!this.props.disabled && <button className="buttons-panel__button" type="button" onClick={() => (this.props.handleSubmit(false), this.props.formikProps.isValid? '' : this.onValidationFail())} >Save</button>}</Link>
The method is(it's basically only to display a popup error, and it's working):
onValidationFail() {
setAlertMessage("You need to fill all mandatory fields")
}
So far the best I've achieved is that it works as intended in every case, but if the form hasn't been touched at all. I've tried several different ways, including formValidate from formik, for some reason it didn't work too.
I'm looking forward to some tips/hints/solutions on the case.
I think that your solution looks bit overcomplicated. Maybe just use simple form submit, and after form submission redirect user to route.
It would look like that:
import { Formik, Form, Field } from 'formik'
class MyForm {
render() {
return (
<Formik
initialValues={...}
onSubmit={() => {this.props.history.push('/go-somewhere')}}
>
<Form>
<Field name="firstName" type="text" />
<button type="submit">Submit</button>
</Form>
</Formik>
)
}
}

Redux Form Semantic UI React Dropdown meta:touched not working

Redux Form Semantic UI React Dropdown meta:touched not working
My Redux form has two Semantic UI React Dropdown components. One dropdown the user selects a month (January thru December) and the other the user selects a year (2020 thru 2030). I have a requirement to validate that the month and year provider by the user is not in the past. For example, it is now May 2020 and if the user selects January 2020 an error needs to be shown to the user.
I’ve created a validator that properly works and creates an error for the above condition. The problem is that the Dropdown component’s “touched” meta is always false so the error is never displayed to the user.
Code for my DropdownInput component is shown below.
If a remove “touched” from the error label, the error displays immediately before the user has a chance to input their date… not a good user experience.
Any suggestions as to how to get the Dropdown component to update to “true” when the user makes a selection would be appreciated. I’ve also used the Semantic UI React Select component in place of the Dropdown component and have the same result.
Thanks in advance for your suggestions.
import React from 'react';
import { Form, Label, Dropdown } from 'semantic-ui-react';
const SelectInput = ({
input,
multiple,
options,
placeholder,
size,
style,
label,
meta: { touched, error },
}) => {
console.log(`DropdownInput: touched=${touched} error=${error}`);
return (
<Form.Field error={touched && !!error}>
{label && <label>{label}</label>}
<Dropdown
selection
value={input.value || null}
onChange={(e, data) => input.onChange(data.value)}
multiple={multiple}
options={options}
placeholder={placeholder}
size={size}
style={style}
/>
{touched && error && (
<Label basic color='red'>
{error}
</Label>
)}
</Form.Field>
);
};
export default SelectInput;

How to deal with multiple <select> dropdown menus in the same class component that use the same state to pass a value to redux?

This code works fine if the user selects something from each dropdown menu, but if they forget to make a selection, it will just use the value selected from the previous dropdown menu. Also if they don't make any selection at all and submit, it will obviously submit the default value stored in the state which is "0".
Anyone happen to have a workaround for this? Thanks.
export class Content extends Component {
constructor(props){
super(props)
this.state = {
selectedOption: 0
}
}
handleOptionChange = e => {
this.setState({
selectedOption: e.target.value
})
}
handleSubmit = e => {
e.preventDefault()
}
render() {
let snowboardItems = this.props.snowboards.map((board,index) => {
return <div><form onSubmit={this.handleSubmit}>
<li key={index} className="list_item">
<div className="content_div1">
<h3>{board.name}</h3>
<h3>$ {board.price}</h3>
<h4>{board.terrain}</h4>
<h4>Shape: {board.shape}</h4>
<p>Board Length:</p>
<select value={this.state.selectedOption} onChange={this.handleOptionChange}>
{board.length.map((item, index) =>
<option value={item} key={index}>{item}</option>
)}
</select> cm
</div>
<div className="content_div2">
<button className="content_button" type="submit" onClick={() => this.props.addToCart({board}, this.state.selectedOption)}>Add to Cart</button>
<img className="image" src={board.imageurl} />
</div>
</li>
</form>
</div>
})
This is really a case where you should separate this into two components: one to render the list of items (you could do this in the parent passing the props too), and another to render the item and possibly handle its state.
If for some reason you can't though, you'll probably want to separate each board option into its own property on state. Here's an example where state is updated dynamically:
https://codesandbox.io/embed/snowboards-pl9r5
You should always code defensively, so in the example there's a "short circuit" check to make sure that a length was selected before adding it to the cart. Also the select field is marked as required so that you can use HTML5 as another fallback validator.
You can check it by trying to add an item without a length and also selecting different options and adding them to the cart (logging them in the console).
On another note: I changed it to more specific keys because mapping multiple lists and using the index as a key will result in duplicate keys. Keys are how react knows which item is which, and you don't want to confuse react!
P.S. Way to bum me out giving a snowboard example in the summer! lol Happy hackin'

Is there a way to get access to redux-form errors from inside the Container with reduxForm V7?

Previously, in Redux-Form V5, I was accessing errors as email.error and password.error from inside the render function.
In V7, my CSS is giving me grief to do it this way:
const renderInput = (field) => {
const { meta: { touched, error } } = field
return (
<div>
<input type={field.type} placeholder={field.placeholder} {...field.input} />
<div className="inputErrorText">
{touched ? error : ''} // this doesnt work due to my CSS
</div>
</div>
)
}
// ...inside the Container:
<div className="ui left icon input">
<i className="user icon"></i>
<Field
name="email"
component={renderInput}
type="text"
placeholder="Enter your Email"
/>
</div>
// Below is the old way I was doing it
{/*email.touched && email.error && <div className="inputErrorText">{email.error}</div>*/}
If I render the errors via renderInput(), the CSS gets mangled due to what looks like some position relative+absolute reasons, so I need the errors to render below that last div at the bottom of my code above.
How can I deal with rendering the field errors outside renderInput() and/or elsewhere in the <form>. I think my question centers around the field input parameter of renderInput(). How to access it inside the Container? Is it available on this.props anywhere?
I just got it working. I found this source: Redux-form 6.0.0 access error outside Field component which illustrates a way to use the Fields component of redux-form V6 & 7.
I wasn't able to get it working as shown there. I'm not entirely sure at this point, but it was throwing all kinds of "cannot read property touched of undefined". I tried many combinations of if (!fields) return else destructure, and about 20 different combinations and I just couldn't get it to iterate through using Object.keys(). I was able to console log easily fields.email.input and see everything, but as soon as I started digging into meta, it started throwing undefined even while it was logging the values sometimes. The weird thing is untouched was always false not undefined, so it was fairly infuriating, but I digress.
Another person may have more luck with that above solution. Fields is definitely what a person wants to use to gain control over errors in Redux-form V6-7.
I did however, get it to work like this:
import { reduxForm, Field, Fields } from 'redux-form'
const fieldNames = [
'email',
'password'
]
const renderInput = (field) => {
return (
<input type={field.type} placeholder={field.placeholder} {...field.input} />
)
}
// inside the Container (class methods)
renderEmailError({ email }) {
const { touched, error } = email.meta
if (touched && error) return <div className="inputErrorText">{error}</div>
return null
}
renderPasswordError({ password }) {
const { touched, error } = password.meta
if (touched && error) return <div className="inputErrorText">{error}</div>
return null
}
// Inside render function
<div className="field">
<div className="ui left icon input">
<i className="user icon"></i>
<Field
name="email"
component={renderInput}
type="text"
placeholder="Enter your Email"
/>
</div>
<Fields names={fieldNames} component={this.renderEmailError} />
</div>
x
I was not able to get it working with the "standard" implementation using Field because my CSS simply does not allow for a div to be placed along with the input. I could not alter the CSS because of the way it places the icons on top of the input fields.
I experimented with moving the entire 'fieldset' into the renderInput() function, but I became equally stuck because of the onClick handler when you click the eyeball to expose the password (UX substitute for passwordConfirm field). I was not able to update the component state from outside the class, nor was I able to call the function properly from outside the class.
Additionally, I also learned that you cannot manipulate an input's type if it is password using document.querySelector('input').type because it is a security risk.

How to pre populate an "redux form's" input fields after form has already rendered

I am using redux form for a React app I am working on, the redux form version is V.6. I am also using a wizard style form(so after each fieldset is completed the user can click a "proceed to next step" button to fill out the next section of the form). So what I want to happen is that when the user fills out the "shipping and delivery" fieldset, I want the "billing and payment" feildset(the next feildset in the form) to auto fill with the values entered in the "shipping and delivery" fieldset. They both are identical fieldsets.
Unfortunately it seems like I will not be able to use the intialValues prop since the form will already be rendered when the users enters their values into the form. I also run into issues setting values in the redux form manually in the backend as redux form doesn't seem to be a fan of this method. I can set the value manually, but then I can no longer update it with the input field
In hindsight I wish I built the form more like the example wizard form on the redux form webpage where each "feildset" is actually its own form, but as of right now I can't go back and make those changes, so it all needs to stay as one big form.
If anyones has any advice or opinions please let me know!
The form dumbed down looks like this:
<form>
<ShippingAndDelivery
checkFieldsetProgress={this.checkFieldsetProgress}
handleUpdatingDeliveryType={handleUpdatingDeliveryType}
availableShippingTypes={availableShippingTypes}
shippingType={shippingType}
handleShippingRequest={handleShippingRequest}
recieveShippingError={recieveShippingError}
shippingError={shippingError}
/>
<BillingAndPayment
checkFieldsetProgress={this.checkFieldsetProgress}
handlePopulateBillingAddress={handlePopulateBillingAddress}
promoCodeValue={promoCode}
/>
</form>
It is also in an es6 class so i can mess with states and everything if I need to.
The fieldset components(so ShippingAndDelivery and BillingAndPayment) use the and component like this:
class shippingAndDelivery extends React.Component {
render () {
const {
whatEverPropsNeeded
} = this.props;
return (
<div>
<Fields
names={[
'address_shipping',
'email_shipping'
]}
component={RenderedFields}
whatEverPropsNeeded={whatEverPropsNeeded}
/>
</div>
)
}
}
const RenderedFields = ( fields ) => (
<div className={style.container} onChange={fields.updateProgress(fields)}>
<Col md={8}>
<fieldset>
<div className={style.title}>
<p>Shipping Address</p>
</div>
<Col md={12}>
<BasicInput
input={fields.email_shipping.input}
meta={fields.email_shipping.meta}
disabled={false}
placeholder="Email Address"
/>
</Col>
<Col md={12}>
<BasicInput
input={fields.address_shipping.input}
meta={fields.address_shipping.meta}
disabled={false}
placeholder="Address Line 1"
/>
</Col>
</div>
</fieldset>
</div>
);
You can use the enableReinitialize prop of reduxForm to re-initialize the form any time initialValues changes, thus enabling you populate the downstream fields with values from the upstream.
I would use formValueSelector to extract the values from the redux store and set them to initialValues. Use the keepDirtyOnReinitialize prop to make sure reinitialization does not overwrite your unsaved changes to the shipping address. Example:
const selector = formValueSelector('MyFormName')
const mapStateToProps = reducers => {
// obtain shipping address values from the redux store
const {
address,
addressOptional,
city,
zipCode,
country
} = selector(reducers, 'shipping')
return {
initialValues: {
// shipping address is empty initially
shipping: {
address: '',
addressOptional: '',
city: '',
zipCode: '',
country: ''
},
// billing address initial values are changed any time
// the shipping address values are changed
billing: {
address,
addressOptional,
city,
zipCode,
country
}
}
}
}
#connect(mapStateToProps)
#reduxForm({
form: 'MyFormName',
// reinitialize the form every time initialValues change
enableReinitialize: true,
// make sure dirty fields (those the user has already edited)
// are not overwritten by reinitialization
keepDirtyOnReinitialize: true
})
class MyFormContainer extends MyFormComponent {}
Hope this helps!

Resources