I want to add multiple persons dynamically in my form. Like I have Person 1 username and email then when I click Add Person it should make same fields for person 2 on the same page. When I click the Submit button it should give me the object of all persons.
App.js
import './App.css';
import React, { Component } from 'react'
import PropTypes from 'prop-types'
export default class App extends Component {
state = {
fields:[]
};
addPerson() {
this.setState({fields:[...this.state.fields, ""]})
};
handleChange(e, index) {
this.state.fields[index] = e.target.value;
this.setState({fields: this.state.fields});
}
handleSubmit(e) {
console.log(this.state,"$$")
}
render() {
return (
<div className="App">
<header className="App-header">
<div>
<h1>The Form</h1>
{
this.state.fields.map((field, index) => {
return(
<div key={index}>
<input onChange={(e)=>this.handleChange(e, index)} value={field}/>
</div>
)
}
)
}
<button onClick={(e) => this.addPerson(e)}>Add Person</button>
<button onClick={(e) => this.handleSubmit(e)}>Submit</button>
</div>
</header>
</div>
)
}
}
I want my state would be like this...
state = {
fields:[
{
id: 1,
name: 'Max',
email: 'max.max#max.in'
}
]
};
Demo of my current page.
This is my solution codesandbox
You need to have two inputs, for email and name, and depending on which input is updated, update the value of person in array.
import React, { Component } from "react";
import "./styles.css";
export default class App extends Component {
state = {
fields: []
};
addPerson() {
const newPerson = {
id: Math.random(),
name: "",
email: ""
};
this.setState({ fields: [...this.state.fields, newPerson] });
}
handleChange(e, index) {
const fieldsCopy = [...this.state.fields];
fieldsCopy.forEach(item => {
if (item.id === index) {
item[e.target.name] = e.target.value;
}
});
this.setState({ fields: fieldsCopy }, () => console.log(this.state.fields));
}
handleSubmit(e) {
console.log(this.state, "$$");
}
render() {
return (
<div className="App">
<header className="App-header">
<div>
<h1>The Form</h1>
{this.state.fields.map(field => {
return (
<div key={field.id}>
<input
onChange={e => this.handleChange(e, field.id)}
name="name"
/>
<input
onChange={e => this.handleChange(e, field.id)}
name="email"
/>
</div>
);
})}
<button onClick={e => this.addPerson(e)}>Add Person</button>
<button onClick={e => this.handleSubmit(e)}>Submit</button>
</div>
</header>
</div>
);
}
}
Edited:
Here is my version of it:
import './App.css';
import React, { Component } from 'react'
import PropTypes from 'prop-types'
export default class App extends Component {
index = 0;
state = {
fields: []
};
handleChange(e, idx) {
const { name, value } = e.target;
this.setState(state => {
return state.fields[idx][name] = value;
});
}
addPerson = () => {
const person = { id: this.index, name: '', email: '' };
this.index++;
this.setState({ fields: [ ...this.state.fields, person ] })
}
handleSubmit = () => {
console.log(this.state.fields);
}
render() {
const { fields } = this.state;
return (
<div className="App">
<header className="App-header">
<div>
<h1>The Form</h1>
{fields.length
? fields.map((field, idx) => (
<div key={idx}>
<label>Name:</label>
<input type="text" onChange={(e)=>this.handleChange(e, idx)} name="name" value={field.name}/>
<label>Email:</label>
<input type="email" onChange={(e)=>this.handleChange(e, idx)} name="email" value={field.email}/>
</div>
))
: null
}
<button onClick={this.handleSubmit}>Submit</button>
<button onClick={() => this.addPerson()}>Add Person</button>
</div>
</header>
</div>
)
}
}
If you are using the person id as unique identifier outside this component's state, I would suggest using some id generator library like uuid.
I hope this helps!
Related
I'm trying to make an app where an 'Owner' can have multiple 'cars', I have my App.js file where the Owner enters there name and can enter there car details ('Car name' and 'Car Type'), a Owner can have multiple Cars, and when they click 'Add car entry' a Component where they enter there car details called 'OwnersCars' is repeated. Like so
to
If an owner fills out the input boxes in this component (For X amount of cars) then clicks 'Save Owner' i want the owner aswell as a list of all there cars to be saved into one State.
Currently i have my app.js file like this (count is used to know the number of OwnersCars divs)
import './App.css';
import React, {useState, useRef} from 'react';
import OwnersCars from './ownersCars';
function App() {
const [count, setCount] = useState(1)
const [OwnerInput, SetOwnerInput] = useState({
id: "",
Name: "",
cars: []
});
const [newCarInput, SetnewCarInput] = useState({
id: "",
Type: "",
CarName: ""
});
const removeDiv = () => {
//console.log('sw\nag')
setCount(count - 1)
}
const repeatDiv = () => {
//console.log('sw\nag')
setCount(count + 1)
}
const displayCarInput = (e) => {
//console.log(count, "<--key")
return ( ([...Array(count)].map((e, i) => <OwnersCars onAddNameCar={addNewCarNameHandler} onAddTypeCar={addNewCarTypeHandler}></OwnersCars> )))
}
const displayRemove = (e) =>{
if (count > 1) {
return (<button className='removeAnimalButton' onClick={removeDiv}> <dt> Remove Last Animal Entry</dt></button>)
}
}
const NameHandler = (e) => {
//console.log(e.target.value)
SetOwnerInput((prevState) => {
return { ...prevState, Name: e.target.value };
});
}
const submitHandler = (event) => {
event.preventDefault();
const value = Math.random().toString()
const OwnerData = {
id: value,
Name: OwnerInput.Name,
cars: [newCarInput]
};
console.log(OwnerData, "<--- ownerdata with cars data");
}
const addNewCarNameHandler = (values) => {
//console.log(values, "<---5")
SetnewCarInput((prevState) => {
return { ...prevState, CarName: values };
});
};
const addNewCarTypeHandler = (values) => {
//console.log(values, "<---5")
SetnewCarInput((prevState) => {
return { ...prevState, Type: values };
});
};
return (
<div>
<div>
<div>
<label for="exampleInputPassword1"></label>
<button onClick={submitHandler} ><dt>Save Owner</dt></button>
</div>
</div>
<hr/>
<div className="wrapper">
<div class="new-owner-div">
<h5>Owner</h5>
<hr/>
<form>
<div>
<input type="name" id="exampleInputClinic" placeholder="Owner Name" onChange={NameHandler}/>
</div>
</form>
</div>
<div class="new-owner-div-2">
<h5>Owners Cars</h5>
<hr/>
{displayCarInput()}
<div>
<button onClick={repeatDiv}> <dt> Add Car Entry</dt></button>
{displayRemove()}
</div>
</div>
</div>
</div>
);
}
export default App;
and i have my ownersCars.js file with the OwnersCars component like this
import React, {useState, useRef} from 'react';
function OwnersCars(props) {
const CarNameHandler = (e) => {
console.log(e.target.value)
props.onAddNameCar(e.target.value)
}
const CarTypeHandler = (e) => {
console.log(e.target.value)
props.onAddTypeCar(e.target.value)
}
return(
<div>
<div>
<div>
<h3>Car name</h3>
<span></span>
<h3>Type</h3>
</div>
<div>
<div>
<input placeholder="Car Name" onChange={CarNameHandler}/>
</div>
<span class="span1-box"></span>
<div class="height">
<input class="input-box-OA-2" placeholder="Car Type" onChange={CarTypeHandler}/>
</div>
<span class="span1-box"></span>
</div>
</div>
</div>
)
}
export default OwnersCars
but when i click save user it only saves the latest car entry!
Would anyone be able to help?
Sorry for the mess and lack of css i removed a bunch of things from the original code so it was easier to follow on StackOverflow. Also im fairly new to react so im sure theres alot of things that need to be changed for this to work.
You need to push to owner cars, every time you add a new car. Please find the code below for App.js changes. check repeatDiv. Similarly, you need to pop from cars the particular car with remove div which I leave it to you
import React, { useState, useRef } from "react";
import OwnersCars from "./Owner";
function App() {
const [count, setCount] = useState(1);
const [OwnerInput, SetOwnerInput] = useState({
id: "",
Name: "",
cars: []
});
const [newCarInput, SetnewCarInput] = useState({
id: "",
Type: "",
CarName: ""
});
const removeDiv = () => {
//console.log('sw\nag')
setCount(count - 1);
};
const repeatDiv = () => {
//console.log('sw\nag')
OwnerInput.cars.push(newCarInput);
setCount(count + 1);
};
const displayCarInput = (e) => {
//console.log(count, "<--key")
return [...Array(count)].map((e, i) => (
<OwnersCars
onAddNameCar={addNewCarNameHandler}
onAddTypeCar={addNewCarTypeHandler}
></OwnersCars>
));
};
const displayRemove = (e) => {
if (count > 1) {
return (
<button className="removeAnimalButton" onClick={removeDiv}>
{" "}
<dt> Remove Last Animal Entry</dt>
</button>
);
}
};
const NameHandler = (e) => {
//console.log(e.target.value)
SetOwnerInput((prevState) => {
return { ...prevState, Name: e.target.value };
});
};
const submitHandler = (event) => {
event.preventDefault();
const value = Math.random().toString();
const OwnerData = {
id: value,
Name: OwnerInput.Name,
cars: OwnerInput.cars
};
console.log(OwnerData, "<--- ownerdata with cars data");
};
const addNewCarNameHandler = (values) => {
//console.log(values, "<---5")
SetnewCarInput((prevState) => {
return { ...prevState, CarName: values };
});
};
const addNewCarTypeHandler = (values) => {
//console.log(values, "<---5")
SetnewCarInput((prevState) => {
return { ...prevState, Type: values };
});
};
return (
<div>
<div>
<div>
<label for="exampleInputPassword1"></label>
<button onClick={submitHandler}>
<dt>Save Owner</dt>
</button>
</div>
</div>
<hr />
<div className="wrapper">
<div class="new-owner-div">
<h5>Owner</h5>
<hr />
<form>
<div>
<input
type="name"
id="exampleInputClinic"
placeholder="Owner Name"
onChange={NameHandler}
/>
</div>
</form>
</div>
<div class="new-owner-div-2">
<h5>Owners Cars</h5>
<hr />
{displayCarInput()}
<div>
<button onClick={repeatDiv}>
{" "}
<dt> Add Car Entry</dt>
</button>
{displayRemove()}
</div>
</div>
</div>
</div>
);
}
export default App;
And the output with cars saved
I have one question. I created contact form in react and I want to collapsed only single contact, which was clicked. Toggle is method which should collapse it. And colapse is state. My problem is that when I click it affect all contact and all are collapsed. How can I improve it?
ContactBook.js
import React, { Component } from "react";
import Contact from "../Contact/Contact";
import "./ContactBook.css";
class ContactBook extends Component{
constructor(props){
super(props);
this.state = {
colapse :true,
contacts: [
{
id: 1,
name: 'Propulsion Academy',
address: 'Zurich',
avatar: 'propulsion-academy-logo.png'
},
{
id: 2,
name: 'Propulsion Academy',
address: 'Luzern',
avatar: 'propulsion-academy-logo.png'
},
{
id: 3,
name: 'Propulsion Academy',
address: 'Munich',
avatar: 'propulsion-academy-logo.png'
},
],
};
}
toggle=()=>{
const doesShow = this.state.colapse;
this.setState({colapse: !doesShow});
}
deleteContact=(contactIndex)=>{
//with slice method we create copy of an array
const contacts =this.state.contacts.slice();
contacts.splice(contactIndex, 1);
this.setState({contacts: contacts})
}
//get name from input
addName = e =>{
this.setState({
name: e.target.value,
})
}
//get address from input
addAddress = e =>{
this.setState({
address: e.target.value,
})
}
//update state on button click
handleSubmit = (e) =>{
e.preventDefault()
if(this.state.name && this.state.address) {
this.setState(state =>{
const newContact = {
id: Math.max(...state.contacts.map(c => c.id))+1,
name: this.state.name,
address: this.state.address,
}
return{
contacts:[...state.contacts, newContact]
}
})
}
}
render() {
return (
<div className="contactBook">
<form className ="addContact" >
<p>New Contact</p>
<label id="name"><p>Name</p><input type='text' id="name" onChange={this.addName}/></label>
<label id="address"><p>Address:</p><input type='text' id="address" onChange={this.addAddress} /></label>
<input type='file' name='file' />
<button type='submit' onClick= {this.handleSubmit}>SUBMIT</button>
</form>
<div className="contacts">
{this.state.contacts.map((contact, index) =>
< Contact key={contact.id} contact={contact} delete={()=>this.deleteContact(index)} colapse={this.state.colapse} toggle={this.toggle}/>)
}
</div>
</div>
);
}
};
export default ContactBook;
Contact.js
import React from "react";
import "./Contact.css";
import avatar from '../assets/user.png'
const Contact = (props) =>{
return (
<div className = "col" >
<img src={avatar} alt="avatar" onClick={props.toggle}/>
{props.colapse === true ?
<div>
<p>Name: {props.contact.name}</p>
<p>Address: {props.contact.address}</p>
<button onClick={props.delete}> Delete </button>
</div> : null
}
</div>
)
};
export default Contact;
I recommend to you to move the collapse and his method to the Contact component it self like this :
const Contact = (props) =>{
[collapse,setCollapse] = useState(true)
return (
<div className = "col" >
<img src={avatar} alt="avatar" onClick{()=>setCollape(prev=>!prev)}/>
{collapse === true ?
<div>
<p>Name: {props.contact.name}</p>
<p>Address: {props.contact.address}</p>
<button onClick={props.delete}> Delete </button>
</div> : null
}
</div>
)
};
In this component I created a state that will manage the collapse for each of the component the render in the map.
further more, the prev give you the last value you submit and it's best practice to use the prev instead of just setCollapse(!collapse)
You have one function for all contacts, and since you use .map() they will all behave the same, since toggling one toggles the state which is used to render all individual contacts. The solution would be to pass the selected contact in your state so your app actually knows which one is to be rendered! Hopefully that makes sense!
Good luck and let us know how things work out!
This is it:
ContactBook.js
import React, { Component } from "react";
import Contact from "./Contact";
// import "./ContactBook.css";
class ContactBook extends Component {
constructor(props) {
super(props);
this.state = {
contacts: [
{
id: 1,
name: "Propulsion Academy",
address: "Zurich",
avatar: "propulsion-academy-logo.png",
colapse: true
},
{
id: 2,
name: "Propulsion Academy",
address: "Luzern",
avatar: "propulsion-academy-logo.png",
colapse: true
},
{
id: 3,
name: "Propulsion Academy",
address: "Munich",
avatar: "propulsion-academy-logo.png",
colapse: true
}
]
};
}
// toggle = () => {
// const doesShow = this.state.colapse;
// this.setState({ colapse: !doesShow });
// };
deleteContact = contactIndex => {
//with slice method we create copy of an array
const contacts = this.state.contacts.slice();
contacts.splice(contactIndex, 1);
this.setState({ contacts: contacts });
};
togglecontact = contactIndex => {
let contacts = this.state.contacts.slice();
contacts[contactIndex].colapse = !contacts[contactIndex].colapse;
this.setState({ contacts: contacts });
};
//get name from input
addName = e => {
this.setState({
name: e.target.value
});
};
//get address from input
addAddress = e => {
this.setState({
address: e.target.value
});
};
//update state on button click
handleSubmit = e => {
e.preventDefault();
if (this.state.name && this.state.address) {
this.setState(state => {
const newContact = {
id: Math.max(...state.contacts.map(c => c.id)) + 1,
name: this.state.name,
address: this.state.address
};
return {
contacts: [...state.contacts, newContact]
};
});
}
};
render() {
return (
<div className="contactBook">
<form className="addContact">
<p>New Contact</p>
<label id="name">
<p>Name</p>
<input type="text" id="name" onChange={this.addName} />
</label>
<label id="address">
<p>Address:</p>
<input type="text" id="address" onChange={this.addAddress} />
</label>
<input type="file" name="file" />
<button type="submit" onClick={this.handleSubmit}>
SUBMIT
</button>
</form>
<div className="contacts">
{this.state.contacts.map((contact, index) => (
<Contact
key={contact.id}
contact={contact}
togglecontact={() => this.togglecontact(index)}
delete={() => this.deleteContact(index)}
colapse={this.state.colapse}
toggle={this.toggle}
/>
))}
</div>
</div>
);
}
}
export default ContactBook;
contactbook.js
import React from "react";
// import "./Contact.css";
const Contact = props => {
let buffer;
props.contact.colapse === true
? (buffer = (
<div>
<p>Name: {props.contact.name}</p>
<p>Address: {props.contact.address}</p>
<button onClick={props.delete}> Delete </button>
</div>
))
: null;
return (
<div className="col">
<img
onClick={props.togglecontact}
src="data:image/jpeg;base64,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"
/>
{buffer}
</div>
);
};
export default Contact;
can check it at:https://stackblitz.com/edit/react-mrvrr1?file=src%2FContact.js
only Toggling single element.
I would like to use the same form to add new item and update existing. I tried to do it on my own, unfortunately unsuccessfully. I just created method findItemToEdit, but I do not know how to pass the information to existing form, edit it and update the item. As I am new in React I got a little confused. Could anybody give me help, please.
Her is my context.js file:
import React, { Component, createContext } from "react";
import uuid from "react-uuid";
import { warehouseProducts } from "./data";
const ProductContext = createContext();
class ProductProvider extends Component {
state = {
products: [],
detailProduct: "",
editItem: null,
};
componentDidMount() {
this.setProducts();
}
setProducts = () => {
let products = [];
warehouseProducts.forEach((item) => {
const singleItem = { ...item };
products = [...products, singleItem];
});
this.setState(() => {
return { products: products };
});
};
getItem = (id) => {
const product = this.state.products.find((item) => item.id === id);
return product;
};
productDetailHandler = (id) => {
const product = this.getItem(id);
this.setState(() => {
return { detailProduct: product };
});
};
changeHandler = (event) => {
const value = event.target.value;
const { newProductItem } = this.state;
this.setState({
newProductItem: { ...newProductItem, [event.target.name]: value },
});
};
addItemHandler = ( event, name, ean, type, weight, color, quantity, price, info) => {
event.preventDefault();
event.target.reset();
const { newProductItem } = this.state;
const products = [
...this.state.products,
{ ...newProductItem, id: uuid() },
];
this.setState({ products, newProductItem: {} });
};
removeProduct = (id) => {
const products = this.state.products.filter((item) => item.id !== id);
this.setState({ products: products });
};
// find item for editing
findItemToEdit = (id) => {
const product = this.getItem(id);
this.setState({ editItem: product });
};
render() {
return (
<ProductContext.Provider
value={{
...this.state,
productDetailHandler: this.productDetailHandler,
changeHandler: this.changeHandler,
addItemHandler: this.addItemHandler,
removeProduct: this.removeProduct,
findItemToEdit: this.findItemToEdit,
}}
>
{this.props.children}
</ProductContext.Provider>
);
}
}
const ProductConsumer = ProductContext.Consumer;
export { ProductProvider, ProductConsumer };
and Form component:
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { ProductConsumer } from "../../context";
import "./ProductForm.css";
class ProductForm extends Component {
render() {
return (
<ProductConsumer>
{(value) => (
<form
className="Form"
onSubmit={value.addItemHandler}
>
<h1>Item Input Form</h1>
<p>
Item Name{" "}
<input
onChange={value.changeHandler}
className="Form_input"
type="text"
name="name"
value={value.products.name}
required
/>
</p>
<p>
EAN{" "}
<input
onChange={value.changeHandler}
className="Form_input"
type="text"
name="ean"
value={value.products.ean}
required
/>
</p>
<p>
Type{" "}
<input
onChange={value.changeHandler}
className="Form_input"
type="text"
name="type"
value={value.products.type}
required
/>
</p>
<p>
Weight, kg{" "}
<input
onChange={value.changeHandler}
className="Form_input"
type="number"
name="weight"
value={value.products.weight}
required
/>
</p>
<p>
Color{" "}
<input
onChange={value.changeHandler}
className="Form_input"
type="text"
name="color"
value={value.products.color}
required
/>
</p>
<p>
Quantity{" "}
<input
onChange={value.changeHandler}
className="Form_input"
type="number"
name="quantity"
value={value.products.quantity}
required
/>
</p>
<p>
Price, ${" "}
<input
onChange={value.changeHandler}
className="Form_input"
type="number"
name="price"
value={value.products.price}
required
/>
</p>
<p>
Description{" "}
<textarea
onChange={value.changeHandler}
className="Form_info"
type="text"
name="info"
value={value.products.info}
required
/>
</p>
<div>
<button className="Btn" type="submit">
ADD
</button>
<Link to="/products">
<button className="Back">BACK TO PRODUCTS</button>
</Link>
</div>
</form>
)}
</ProductConsumer>
);
}
}
export default ProductForm;
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"));
I have this wrapper class that is used because I am using Formik and the FieldArray
import React, { Component } from "react";
import { ReactDOM } from "react-dom";
import Select from "react-select";
import { observer } from "mobx-react";
import { axiosInstance } from "../stores/AxiosInstance";
#observer
export default class CountryStateSelectComponent extends React.Component {
constructor(props) {
super(props);
this.state = { stateOptions: [] };
}
handleCountryChange = value => {
const that = this;
axiosInstance
.get(`/States?countryId=${value.value}`)
.then(function(response) {
that.props.onChange(that.props.countryName, value);
that.props.onChange(that.props.stateName, null);
const states = response.data.map(state => {
return { label: state.name, value: state.id };
});
// if I move out state select code then won't need to update state here but don't know how to call something like updateState(record)
that.setState({
stateOptions: states
});
});
};
handleStateChange = value => {
console.log(this.props.stateName, value)
this.props.onChange(this.props.stateName, value);
};
handleCountryBlur = () => {
this.props.onBlur(this.props.countryName, true);
};
handleStateBlur = () => {
this.props.onChange(this.props.stateName, true);
};
render() {
const props = this.props;
return (
<React.Fragment>
<div className="field">
<label className="label">Country</label>
<div className="control">
<Select
options={props.options}
isMulti={props.isMulti}
onChange={this.handleCountryChange}
onBlur={this.handleCountryBlur}
closeMenuOnSelect={props.closeMenuOnSelect}
/>
{this.props.CountryError}
</div>
</div>
<div className="field">
<label className="label">State/Province</label>
<div className="control">
<Select
options={this.state.stateOptions}
onChange={this.handleStateChange}
onBlur={this.handleStateBlur}
/>
{this.props.StateError}
</div>
</div>
</React.Fragment>
);
}
}
However what I found is that when the State gets selected the value does not get stored in Formik(it gets stored as undefined and sometimes true).
So now I am thinking maybe moving out the State Zip out and making it's own wrapper or something but I don't know how to get the "states" that came back and populate the correct state box as they can generate many.
#inject("AccountSetupStore")
#observer
export default class MyComponent extends Component {
constructor(props) {
super(props);
this.state = { records: [this.generateRecord(1, true, true)] };
}
componentDidMount() {
const accountSetupStore = this.props.AccountSetupStore;
accountSetupStore.getCountries();
}
updateState(record) {
// would like to call this method that can somehow update the record
// propblem is I don't seem to have access to props when this function is being called from the CountryStateSelectComponent
}
render() {
const props = this.props;
const accountSetupStore = props.AccountSetupStore;
const countries = [];
for (const country of accountSetupStore.countries) {
countries.push({ value: country.id, label: country.name });
}
return (
<section className="accordions">
<Formik
initialValues={{
records: this.state.records
}}
onSubmit={(
values,
{ setSubmitting, setErrors}
) => {
console.log(values,"values");
}}
validationSchema={Yup.object().shape({
branches: Yup.array()
.of(
Yup.object().shape({
})
)
})}
render={({
values,
setFieldValue,
setFieldTouched,
}) => (
<FieldArray
name="records"
render={arrayHelpers => (
<Form>
{values.records.map((record, index) => {
return (<article}>
<CountryStateSelectComponent options={countries}
onChange={setFieldValue}
countryName={`records[${index}].selectedCountry`}
stateName={`records[0].selectedState`}
onBlur={setFieldTouched}
isMulti={false}
index = {index}
closeMenuOnSelect={true}
CountryError = {<ErrorMessage name={`records[${index}].selectedCountry`}/>}
StateError= {<ErrorMessage name={`records[${index}].selectedState`}/>}
/>
</article>)
})}
</Form>
)}
/>
)}
/>
</section>
);
}
}
React Select onChange sends the value to the method supplied
const onStateChange = (selectedOption, {action}) => {
//do something with the selectedOption according to the action
}
<Select onChange={onStateChange} />
See the documentation for the onChange in the Props documentation.