Search code examples
reactjsgatsbyreact-final-formreact-final-form-arrays

React Final Form: make conditions according to form values


I need to generate a certain number of fields according to a value that the user prompts in the first step of the form.

Since I'm doing a multi-step form with a "Wizard" class <Condition /> component doesn't work.

Basically I need to access to values (pull them from the class inside the functional component) in order to tell React the number of "pages" it needs to generate.

Something like this:

export default () => {(
        <Wizard
            initialValues={{ }}
            onSubmit={onSubmit}
        >
            <Wizard.Page >
                <FirstStep />
            </Wizard.Page>

        {values.items.map((item, index) => (
            <Wizard.Page key="index">
                <SecondStep stepName="item.name" key="index" />
            </Wizard.Page>
        ) )}

        </Wizard>
)}

In order to generate N pages according to the number of items the user created, one page for each item.

This approach does not work because it tells me that the values are not defined.

This is my Wizard React Component:

import React from 'react'
import PropTypes from 'prop-types'
import { Form } from 'react-final-form'
import arrayMutators from 'final-form-arrays'

export default class Wizard extends React.Component {
   static propTypes = {
       onSubmit: PropTypes.func.isRequired
   }
   static Page = ({ children }) => children

   constructor(props) {
       super(props)
       this.state = {
           page: 0,
           values: props.initialValues || {}
       }
   }
   next = values =>
       this.setState(state => ({
           page: Math.min(state.page + 1, this.props.children.length - 1),
           values
       }))

   previous = () =>
       this.setState(state => ({
           page: Math.max(state.page - 1, 0)
       }))

   validate = values => {
       const activePage = React.Children.toArray(this.props.children)[
           this.state.page
       ]
       return activePage.props.validate ? activePage.props.validate(values) : {}
   }

   handleSubmit = values => {
       const { children, onSubmit } = this.props
       const { page } = this.state
       const isLastPage = page === React.Children.count(children) - 1
       if (isLastPage) {
           return onSubmit(values)
       } else {
           this.next(values)
       }
   }

   render() {
       const { children } = this.props
       const { page, values } = this.state
       const activePage = React.Children.toArray(children)[page]
       const isLastPage = page === React.Children.count(children) - 1
       return (
           <Form
               initialValues={values}
               validate={this.validate}
               onSubmit={this.handleSubmit}
               mutators={{...arrayMutators }}
           >
               {({ handleSubmit, submitting, values }) => (
                   <form onSubmit={handleSubmit}>

                       {activePage}

                       <div className="buttons centered">
                           {page > 0 && <button type="button" onClick={this.previous} >« Previous </button>}
                           {!isLastPage && <button type="submit">Next »</button>}
                           {isLastPage && <button type="submit" disabled={submitting}>Submit</button>}
                       </div>

                       <pre>{JSON.stringify(values, 0, 2)}</pre>
                   </form>
               )}
           </Form>
       )
   }
}

And this is the FirstStep component (where the user inserts its first values, it generates and array of items. All I need to do is pull the values of the state in order to generate a number of pages according to the length of that first array):

export default () => (

            <FieldArray name="items">
                {({ fields }) => (
                <div className="centered-items">
                    {fields.map((name, index) => (
                        <div key={name} className="item-row">
                            <label htmlFor={`item-name-${index}`}>item: </label>
                            <Field
                                id={`item-name-${index}`}
                                name={`${name}.item`} 
                                component="input"
                                type="text"
                                placeholder="Place Item Name"
                            />
                            <button type="button" onClick={() => fields.remove(index)}>
                                Remove
                            </button>
                        </div>
                    ))}
                    <button
                        className="centered"
                        type="button"
                        onClick={() => fields.push({ tipologia: '', numero_camere: '1' })}
                    >
                        Add Item
                    </button>
                </div>
                )}
            </FieldArray>
)

Solution

  • I manage to solve this problem calling React.cloneElement() method. This method needs to wrap either activePage variable and the static method "Page", in this way you can pass properties to children, so at least I manage to access the form values inside every page and so create some conditions.

    You can see a working example here: Demo - Codesandbox

    I still haven't figured out how to access this values outside the page (inside Wizard Class but before Wizard.Page method, this could be the ideal way because I can create conditions in order to generate/(or not generate) pages.