So I have built a Wizard Form using React-Final-Form that I am using in my sign-up page. I am trying to figure out how I can display all user inputs on the final page as a way for the user to double-check/verify their inputs before submitting. Any help would be greatly appreciated!
(P.S. - I tried researching this before posting, but all I was able to find was storing user inputs in Redux and accessing them from there, which I'd like to avoid, if at all possible.)
Here is an example link that shows what I want to do - Please feel free to fork and play around with it if you are trying to figure out a solution! https://codesandbox.io/s/0332k02x0v
Here is the code, shortened to include only the relevant bits:
My Wizard.js page:
import React, { Component } from "react";
import PropTypes from "prop-types";
import { Form } from "react-final-form";
class Wizard extends 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}
>
{({ handleSubmit, submitting, values }) => (
<form onSubmit={handleSubmit}>
{activePage}
<div className="buttons">
{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>
);
}
}
export default Wizard;
My index.js page:
import React, { Component } from "react";
import { Field } from "react-final-form";
import formatString from "format-string-by-pattern";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import Wizard from "./Wizard";
import Styles from "./Styles";
import { addUser } from "../../../actions/authActions";
class ReactFinalForm2 extends Component {
state = {};
render() {
const onSubmit = async values => {
this.props.addUser(values);
// API query here
};
const Error = ({ name }) => (
// Error handing here
);
return (
<Styles>
<div>
<Wizard initialValues={{}} onSubmit={onSubmit}>
<Wizard.Page
validate={values => {
// Page validation here
}}
>
// Page inputs here
</Wizard.Page>
<Wizard.Page
validate={values => {
// Page validation here
}}
>
// Page inputs here
</Wizard.Page>
<Wizard.Page
validate={values => {
// Page validation here
}}
>
// Page inputs here
</Wizard.Page>
<Wizard.Page>
{/* *** THIS IS WHERE I WOULD LIKE TO DISPLAY ALL PREVIOUS VALUES (SO THE USER CAN CONFIRM / DOUBLE-CHECK THEIR INPUTS BEFORE SUBMITTING) *** */}
</Wizard.Page>
</Wizard>
</div>
</Styles>
);
}
}
ReactFinalForm2.propTypes = {
addUser: PropTypes.func.isRequired
};
export default connect(
null,
{ addUser }
)(ReactFinalForm2);
I have added a state to the parent component. Changing this state on every submit from the child. I have JSON stringify the state in parent component. As you said no need to use redux, this is the workaround I came with. Still your code has a potential for improvements. Please check this working sandbox:
[ https://codesandbox.io/s/zrvloq4o6x ]
Wizard.js change
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);
}
// Change added
this.props.onStateChange(values);
};
Index.js
import React from "react";
import { render } from "react-dom";
import Styles from "./Styles";
import { Field } from "react-final-form";
import Wizard from "./Wizard";
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const onSubmit = async values => {
await sleep(300);
window.alert(JSON.stringify(values, 0, 2));
};
const Error = ({ name }) => (
<Field
name={name}
subscribe={{ touched: true, error: true }}
render={({ meta: { touched, error } }) =>
touched && error ? <span>{error}</span> : null
}
/>
);
const required = value => (value ? undefined : "Required");
let data = {};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.onStateChange = this.onStateChange.bind(this);
}
onStateChange = values => {
this.setState({ data: values });
console.log(values);
};
render() {
return (
<Styles>
<h1>🏁 React Final Form Example</h1>
<h2>Wizard Form</h2>
<a href="https://github.com/erikras/react-final-form#-react-final-form">
Read Docs
</a>
<p>
Notice the mixture of field-level and record-level (or{" "}
<em>page-level</em> in this case) validation.
</p>
<Wizard
initialValues={{}}
onStateChange={this.onStateChange}
onSubmit={onSubmit}
>
<Wizard.Page>
<div>
<label>First Name</label>
<Field
name="firstName"
component="input"
type="text"
placeholder="First Name"
validate={required}
/>
<Error name="firstName" />
</div>
<div>
<label>Last Name</label>
<Field
name="lastName"
component="input"
type="text"
placeholder="Last Name"
validate={required}
/>
<Error name="lastName" />
</div>
</Wizard.Page>
<Wizard.Page
validate={values => {
const errors = {};
if (!values.notes) {
errors.notes = "Required";
}
return errors;
}}
>
<div>
<label>Best Stooge?</label>
<div>
<label>
<Field
name="stooge"
component="input"
type="radio"
value="larry"
/>{" "}
Larry
</label>
<label>
<Field
name="stooge"
component="input"
type="radio"
value="moe"
/>{" "}
Moe
</label>
<label>
<Field
name="stooge"
component="input"
type="radio"
value="curly"
/>{" "}
Curly
</label>
</div>
</div>
<div>
<label>Notes</label>
<Field name="notes" component="textarea" placeholder="Notes" />
<Error name="notes" />
</div>
</Wizard.Page>
<Wizard.Page>
<div>
<p>
<b>Display all previous values here for user verification </b>
<br />
<i>{JSON.stringify(this.state.data, 0, 2)}</i>
</p>
</div>
</Wizard.Page>
</Wizard>
</Styles>
);
}
}
render(<App />, document.getElementById("root"));