I'm new to ReactJs. I've an getResorts API which returns
data(resortId,name,address,phone,details,placeId,city,state,country) from
1)resort(resortId,name,address,phone,details,placeId) and
2) place table(placeId,city,state,country)
This data from API, I'm binding it to a grid as shown here
I've a separate API to get places data only from places table to bind the dropdowns -city,state and country in modal dialog for add/edit option and pass the selected city Id as the placeId while adding or editing resort as shown here
In ComponentDidUpdate(), after a succesful add or edit of a resort, the this.props has all the resorts of bound to the grid but the record for the updated resort or newly added resort has the city,state and country parts missing even though the resort data(resortId,name,phone,details,placeId) has been successfully added or edited, so my grid look like this after an insert or edit city,state,country missing for record updated or added in this.props
This might be due to the city,state,country not being part of the resort table, how to fix this - the this.props after successful add or edit? How can we know the action status type of saga function successfully executed in ComponentDidUpdate?
CompnentDidUpdate in resort-list.js
componentDidUpdate(prevProps, prevState, snapshot) {
const { resorts } = this.props;
if (!isEmpty(resorts) && size(prevProps.resorts) !== size(resorts)) {
this.setState({ resorts: {}, isEdit: false });
}
}
componentDidMount() {
const { resorts, onGetResorts } = this.props;
const {places, onGetPlaces}=this.props;
if (resorts && !resorts.length) {
onGetResorts();
}
this.setState({ resorts });
if (places && !places.length) {
onGetPlaces();
}
this.setState({ places });
}
<div className="table-responsive">
<BootstrapTable
{...toolkitprops.baseProps}
{...paginationTableProps}
selectRow={selectRow}
defaultSorted={defaultSorted}
classes={
"table align-middle table-nowrap table-hover"
}
bordered={false}
striped={false}
responsive
ref={this.node}
/>
<Modal
isOpen={this.state.modal}
className={this.props.className}
>
<ModalHeader
toggle={this.toggle}
tag="h4"
>
{!!isEdit ? "Edit Resort" : "Add Resort"}
</ModalHeader>
<ModalBody>
<Formik
enableReinitialize={true}
initialValues={{
name:
(resort && resort.name) || "",
address:
(resort && resort.address) ||
"",
phone:
(resort && resort.phone) ||
"",
details:
(resort && resort.details) ||
"",
placeId: (resort && resort.placeId) ||
"",
city:
(resort && resort.city) || "",
state:
(resort && resort.state) || "",
country:
(resort && resort.country) ||
"",
}}
validationSchema={Yup.object().shape({
name: Yup.string().required(
"Please Enter Resort Name"
),
phone: Yup.number().required(
"Please Enter Phone"
),
address: Yup.string().required(
"Please Enter Address"
),
details: Yup.string().required(
"Please Enter Details"
),
/* tags:
Yup.array().required(
"Please Select Tags"
),*/
city:
Yup.string().required(
"Please Enter City"
),
state:
Yup.string().required(
"Please Enter State"
),
country:
Yup.string().required(
"Please Enter Country"
),
})}
onSubmit={values => {
console.log(">>>values>>>>> "+JSON.stringify(values));
if (isEdit) {
const updateResort = {
id: resort.id,
name: values.name,
address: values.address,
phone: values.phone,
details: values.details,
// placeId:updatePlaceId.placeId,
placeId:this.state.placeId,
city: values.city,
state: values.state,
country: values.country,
};
console.log("----update----- "+JSON.stringify(updateResort))
// update user
onUpdateResort(updateResort);
} else {
// const newPlaceId=places.filter(x=>x.city===values["city"]);
const newResort = {
// id: values["id"],
name: values["name"],
address: values["address"],
phone: values["phone"],
details: values["details"],
placeId:this.state.placeId,
// placeId:newPlaceId.placeId
city: values["city"],
state: values["state"],
country: values["country"],
};
console.log("-----new---- "+JSON.stringify(newResort))
// save new resort
onAddNewResort(newResort);
}
this.setState({ selectedResort: null });
this.toggle();
}}
>
{({ errors, status, touched,setFieldValue,handleChange}) => (
<Form>
<Row>
<Col className="col-12">
<div className="mb-3">
<Label className="form-label">
Resort Name
</Label>
<Field
name="name"
type="text"
className={
"form-control" +
(errors.name &&
touched.name
? " is-invalid"
: "")
}
/>
<ErrorMessage
name="name"
component="div"
className="invalid-feedback"
/>
</div>
<div className="mb-3">
<Label className="form-label">
Address
</Label>
<Field
name="address"
type="text"
className={
"form-control" +
(errors.address &&
touched.address
? " is-invalid"
: "")
}
/>
<ErrorMessage
name="address"
component="div"
className="invalid-feedback"
/>
</div>
<div className="mb-3">
<Label className="form-label">
Phone
</Label>
<Field
name="phone"
type="tel"
className={
"form-control" +
(errors.phone &&
touched.phone
? " is-invalid"
: "")
}
/>
<ErrorMessage
name="phone"
component="div"
className="invalid-feedback"
/>
</div>
<div className="mb-3">
<Label className="form-label">
Details
</Label>
<Field
name="details"
type="text"
className={
"form-control" +
(errors.details &&
touched.details
? " is-invalid"
: "")
}
/>
<ErrorMessage
name="details"
component="div"
className="invalid-feedback"
/>
</div>
<div className="mb-3">
<Label className="form-label">
City
</Label>
<Field
onChange={(e) => {
handleChange(e);
let selectedGeo=places.filter((x) => x.city === e.target.value)
if(selectedGeo.length>0)
{
this.setState({placeId:selectedGeo[0].id});
setFieldValue("state", selectedGeo[0].state)
setFieldValue("country", selectedGeo[0].country)
}
}}
type="text"
name="city"
list="datalistCity"
className={
"form-control" +
(errors.city &&
touched.city
? " is-invalid"
: "")
}
multiple={false}
/>
<datalist id="datalistCity">
{
places.map((place) => {
return (
<option
value={place.city}
key={place.id}
>
</option>
);
})}
</datalist>
<ErrorMessage
name="city"
component="div"
className="invalid-feedback"
/>
</div>
<div className="mb-3">
<Label className="form-label">
State
</Label>
<Field
name="state" disabled
type="text"
className="form-control"
/>
<ErrorMessage
name="state"
component="div"
className="invalid-feedback"
/>
</div>
<div className="mb-3">
<Label className="form-label">
Country
</Label>
<Field
type="text"
name="country"
disabled
className="form-control"
/>
<ErrorMessage
name="country"
component="div"
className="invalid-feedback"
/>
</div>
</Col>
</Row>
<Row>
<Col>
<div className="text-end">
<button
type="submit"
className="btn btn-success save-resort"
>
Save
</button>
</div>
</Col>
</Row>
</Form>
)}
</Formik>
</ModalBody>
</Modal>
</div>
ResortsList.propTypes = {
resorts: PropTypes.array,
places:PropTypes.array,
className: PropTypes.any,
onGetResorts: PropTypes.func,
onGetPlaces: PropTypes.func,
onAddNewResort: PropTypes.func,
onDeleteResort: PropTypes.func,
onUpdateResort: PropTypes.func,
};
const mapStateToProps = ({ resorts,places }) => ({
resorts: resorts.resorts,
places:places.places
});
const mapDispatchToProps = dispatch => ({
onGetResorts: () => dispatch(getResorts()),
onGetPlaces: () => store.dispatch(getPlaces()),
onAddNewResort: resort => dispatch(addNewResort(resort)),
onUpdateResort: resort => dispatch(updateResort(resort)),
onDeleteResort: resort => dispatch(deleteResort(resort)),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(withRouter(ResortsList));
In ResortSaga,
const getUserName = () => {
if (localStorage.getItem("authUser")) {
const obj = JSON.parse(localStorage.getItem("authUser"))
return obj;
}
}
function* fetchResorts() {
try {
let obj = getUserName();
const response = yield call(getResorts, obj.accessToken)
console.log("resort list ---- "+JSON.stringify(response))
yield put(getResortsSuccess(response))
} catch (error) {
yield put(getResortsFail(error))
}
}
function* fetchResortDetails({id}) {
try {
const obj = JSON.parse(localStorage.getItem("authUser"));
const response = yield call(getResortDetails,id, obj)
yield put(getResortDetailsSuccess(response))
} catch (error) {
yield put(getResortDetailsFail(error))
}
}
function* onAddNewResort({ payload: resort }) {
try {
let obj = getUserName();
const response = yield call(addNewResort, resort, obj.accessToken)
console.log("------- "+JSON.stringify(response))
yield put(addResortSuccess(response))
} catch (error) {
yield put(addResortFail(error))
}
}
function* onUpdateResort({ payload: resort }) {
try {
//const obj = JSON.parse(localStorage.getItem("authUser"));
console.log("Saga..... edit >>>. "+JSON.stringify(resort))
let obj = getUserName();
const response = yield call(updateResort, resort, obj.accessToken)
yield put(updateResortSuccess(response))
} catch (error) {
yield put(updateResortFail(error))
}
}
function* onDeleteResort({ payload: resort }) {
try {
let obj = getUserName();
console.log("Saga..... delete >>>. "+JSON.stringify(resort))
const response = yield call(deleteResort, resort, obj.accessToken)
yield put(deleteResortSuccess(response))
} catch (error) {
yield put(deleteResortFail(error))
}
}
function* resortsSaga() {
yield takeEvery(GET_RESORTS, fetchResorts)
yield takeEvery(GET_RESORT_DETAILS, fetchResortDetails)
yield takeEvery(ADD_NEW_RESORT, onAddNewResort)
yield takeEvery(UPDATE_RESORT, onUpdateResort)
yield takeEvery(DELETE_RESORT, onDeleteResort)
}
export default resortsSaga
In action.js
import {
GET_RESORT_DETAILS,
GET_RESORT_DETAILS_FAIL,
GET_RESORT_DETAILS_SUCCESS,
GET_RESORTS,
GET_RESORTS_FAIL,
GET_RESORTS_SUCCESS,
ADD_NEW_RESORT,
ADD_RESORT_SUCCESS,
ADD_RESORT_FAIL,
UPDATE_RESORT,
UPDATE_RESORT_SUCCESS,
UPDATE_RESORT_FAIL,
DELETE_RESORT,
DELETE_RESORT_SUCCESS,
DELETE_RESORT_FAIL,
} from "./actionTypes"
export const getResorts = () => ({
type: GET_RESORTS,
})
export const getResortsSuccess = resorts => ({
type: GET_RESORTS_SUCCESS,
payload: resorts,
})
export const getResortsFail = error => ({
type: GET_RESORTS_FAIL,
payload: error,
})
export const getResortDetails = id => ({
type: GET_RESORT_DETAILS,
id,
})
export const getResortDetailsSuccess = resortDetails => ({
type: GET_RESORT_DETAILS_SUCCESS,
payload: resortDetails,
})
export const getResortDetailsFail = error => ({
type: GET_RESORT_DETAILS_FAIL,
payload: error,
})
export const addNewResort = resort => ({
type: ADD_NEW_RESORT,
payload: resort,
})
export const addResortSuccess = resort => ({
type: ADD_RESORT_SUCCESS,
payload: resort,
})
export const addResortFail = error => ({
type: ADD_RESORT_FAIL,
payload: error,
})
export const updateResort = resort => ({
type: UPDATE_RESORT,
payload: resort,
})
export const updateResortSuccess = resort => ({
type: UPDATE_RESORT_SUCCESS,
payload: resort,
})
export const updateResortFail = error => ({
type: UPDATE_RESORT_FAIL,
payload: error,
})
export const deleteResort = resort => ({
type: DELETE_RESORT,
payload: resort,
})
export const deleteResortSuccess = resort => ({
type: DELETE_RESORT_SUCCESS,
payload: resort,
})
export const deleteResortFail = error => ({
type: DELETE_RESORT_FAIL,
payload: error,
})
In actionType.js
/* USERS */
export const GET_RESORTS = "GET_RESORTS"
export const GET_RESORTS_SUCCESS = "GET_RESORTS_SUCCESS"
export const GET_RESORTS_FAIL = "GET_RESORTS_FAIL"
/* USERS PROFILE */
export const GET_RESORT_DETAILS = "GET_RESORT_DETAILS"
export const GET_RESORT_DETAILS_SUCCESS = "GET_RESORT_DETAILS_SUCCESS"
export const GET_RESORT_DETAILS_FAIL = "GET_RESORT_DETAILS_FAIL"
export const ADD_NEW_RESORT = "ADD_NEW_RESORT"
export const ADD_RESORT_SUCCESS = "ADD_RESORT_SUCCESS"
export const ADD_RESORT_FAIL = "ADD_RESORT_FAIL"
export const UPDATE_RESORT = "UPDATE_RESORT"
export const UPDATE_RESORT_SUCCESS = "UPDATE_RESORT_SUCCESS"
export const UPDATE_RESORT_FAIL = "UPDATE_RESORT_FAIL"
export const DELETE_RESORT = "DELETE_RESORT"
export const DELETE_RESORT_SUCCESS = "DELETE_RESORT_SUCCESS"
export const DELETE_RESORT_FAIL = "DELETE_RESORT_FAIL"
In reducer.js
import {
GET_RESORT_DETAILS,
GET_RESORT_DETAILS_FAIL,
GET_RESORT_DETAILS_SUCCESS,
GET_RESORTS,
GET_RESORTS_FAIL,
GET_RESORTS_SUCCESS,
ADD_NEW_RESORT,
ADD_RESORT_SUCCESS,
ADD_RESORT_FAIL,
UPDATE_RESORT,
UPDATE_RESORT_SUCCESS,
UPDATE_RESORT_FAIL,
DELETE_RESORT,
DELETE_RESORT_SUCCESS,
DELETE_RESORT_FAIL,
} from "./actionTypes"
const INIT_STATE = {
resorts: [],
resortDetails: {},
error: {},
}
const resorts = (state = INIT_STATE, action) => {
switch (action.type) {
case GET_RESORTS_SUCCESS:
return {
...state,
resorts: action.payload,
}
case GET_RESORTS_FAIL:
return {
...state,
error: action.payload,
}
case ADD_RESORT_SUCCESS:
return {
...state,
resorts: [...state.resorts, action.payload],
}
case ADD_RESORT_FAIL:
return {
...state,
error: action.payload,
}
case GET_RESORT_DETAILS_SUCCESS:
return {
...state,
resortDetails: action.payload,
}
case GET_RESORT_DETAILS:
return {
...state,
resortDetails: action.payload,
}
case UPDATE_RESORT_SUCCESS:
return {
...state,
resorts: state.resorts.map(resort =>
resort.id.toString() === action.payload.id.toString()
? { resort, ...action.payload }
: resort
),
}
case UPDATE_RESORT_FAIL:
return {
...state,
error: action.payload,
}
case DELETE_RESORT_SUCCESS:
return {
...state,
resorts: state.resorts.filter(
resort => resort.id.toString() !== action.payload.id.toString()
),
}
case DELETE_RESORT_FAIL:
return {
...state,
error: action.payload,
}
case GET_RESORT_DETAILS_FAIL:
return {
...state,
error: action.payload,
}
default:
return state
}
}
export default resorts
So, how do I get type success status from saga for add/edit in ComponentDidMount, so I can add the missing city,state,country manually to the this.props for the resort that was just modified based on resortId? So, when data from this.props get rendered to the grid after add/edit, the places part of the data - city,state and country aren't missing for the resort added or modified from the this.props and the grid.
In Watch, resort before editin this.props and here in watch, city,state,country removed in this.props after successful edit
New to React. I am trying to find out how I can close a Modal after the Form Submit event has been triggered.
export default class UserAdmin extends Component {
constructor(props) {
super(props);
this.state = {
show_user_modal : false
}
}
// Handle User Modal
handleUserModalOpen = () => {
this.setState({ show_user_modal: true});
}
handleUserModalClose = () => {
this.setState({ show_user_modal: false});
}
render() {
const { show_user_modal } = this.state;
return (
<Content>
<div className="site-layout-background">
<div className="contentBody">
<Button type="primary"onClick={this.handleUserModalOpen}>
Add User
</Button>
{show_user_modal && <AddUserModal handleClose={this.handleUserModalClose}/>}
</div>
</div>
</Content>
)
}
}
This works perfectly to open and close the modal, and the submit is working perfectly inside the addUserModal, however I am unsure how I should close the modal after this has been completed. I have tried to setState() from the parent to the child but it doesn't want to even then show the modal. Any help appreciated!
**Adding addUserModal function:
function AddUserModal({handleClose}){
const [addUserForm] = Form.useForm();
/** POST User */
const postUser = (values) => {
axios.post('http://localhost:5000/portal/add-user', values)
.then(res => {
if (res.status === 200) {
console.log(res.data);
}
})
};
return(
<Modal title="Add User" okText="Confirm" visible={true} onCancel={handleClose}
onOk={() => {
addUserForm
.validateFields()
.then((values) => {
postUser(values);
addUserForm.resetFields();
})
.catch((info) => {
console.log('Validate Failed:', info);
});
}}
>
<Form
form={addUserForm}
name="addUserForm"
labelCol={{span: 5,}}
wrapperCol={{span: 16,}}
initialValues={{remember: false,}}
>
<Form.Item label="Username" name="username"
rules={[
{
required: true,
message: 'Please input a username!',
},
]}
>
<Input />
</Form.Item>
<Form.Item label="Email" name="email"
rules={[
{
required: true,
message: 'Please input an email address',
},
]}
><Input />
</Form.Item>
<Form.Item label="Password" name="password"
rules={[
{
required: true,
message: 'Please input a password',
},
]}
><Input.Password />
</Form.Item>
</Form>
</Modal>
);
}
export default AddUserModal;
Your modal has visible property always set to true. Pass show_user_modal variable to the child and use it in the modal visiblestate. The {show_user_modal && <AddUserModal... is unnecessary
handleClose is calling handleUserModalClose. I think the issue is something else. So you can try calling handleClose in .then of your API call and pass the visible prop as well
export default class UserAdmin extends Component {
constructor(props) {
super(props);
this.state = {
show_user_modal : false
}
}
// Handle User Modal
handleUserModalOpen = () => {
this.setState({ show_user_modal: true});
}
handleUserModalClose = () => {
this.setState({ show_user_modal: false});
}
render() {
const { show_user_modal } = this.state;
return (
<Content>
<div className="site-layout-background">
<div className="contentBody">
<Button type="primary"onClick={this.handleUserModalOpen}>
Add User
</Button>
{show_user_modal && <AddUserModal visible={show_user_modal} handleClose={this.handleUserModalClose}/>}
</div>
</div>
</Content>
)
}
}
and use it in AddUserModal
function AddUserModal({visible, handleClose}){
const [addUserForm] = Form.useForm();
/** POST User */
const postUser = (values) => {
axios.post('http://localhost:5000/portal/add-user', values)
.then(res => {
if (res.status === 200) {
console.log(res.data);
handleClose();
}
})
};
return(
<Modal title="Add User" okText="Confirm" visible={visible} onCancel={handleClose}
onOk={() => {
addUserForm
.validateFields()
.then((values) => {
postUser(values);
addUserForm.resetFields();
})
.catch((info) => {
console.log('Validate Failed:', info);
});
}}
>
<Form
form={addUserForm}
name="addUserForm"
labelCol={{span: 5,}}
wrapperCol={{span: 16,}}
initialValues={{remember: false,}}
>
<Form.Item label="Username" name="username"
rules={[
{
required: true,
message: 'Please input a username!',
},
]}
>
<Input />
</Form.Item>
<Form.Item label="Email" name="email"
rules={[
{
required: true,
message: 'Please input an email address',
},
]}
><Input />
</Form.Item>
<Form.Item label="Password" name="password"
rules={[
{
required: true,
message: 'Please input a password',
},
]}
><Input.Password />
</Form.Item>
</Form>
</Modal>
);
}
export default AddUserModal;
I am currently working on a form having checkboxes which has to be validated using react JS. I need it to show an error saying 'Please select atleast 2 checkbox' if less than 2 checkboxes are checked. I've tried using the if condition but its not working. I have referred a lot of of websites but couldn't come up with a proper solution. Please do help me.
MY CODE:
class App extends React.Component {
state = {
checkbox: "",
checkboxValid: false,
errorMsg: {},
};
validateForm = () => {
const { checkboxValid } = this.state;
this.setState({
formValid: checkboxValid,
});
};
updateCheckbox = (checkbox) => {
this.setState({ checkbox }, this.validateCheckbox);
};
validateCheckbox = () => {
const { checkbox } = this.state;
let checkboxValid = true;
let errorMsg = { ...this.state.errorMsg };
if (checkbox.checked < 2) {
checkboxValid = false;
errorMsg.checkbox = "Please select atleast 2 checkbox";
}
this.setState({ checkboxValid, errorMsg }, this.validateForm);
};
render() {
return (
<div>
<label htmlFor="checkbox">checkbox</label>
<ValidationMessage
valid={this.state.checkboxValid}
message={this.state.errorMsg.checkbox}
/>
<input
type="checkbox"
onChange={(e) => this.updateCheckbox(e.target.value)}
/>
Sports
<br></br>
<input
type="checkbox"
onChange={(e) => this.updateCheckbox(e.target.value)}
/>
Business
<br></br>
<input
type="checkbox"
onChange={(e) => this.updateCheckbox(e.target.value)}
/>
Health
<br></br>
<input
type="checkbox"
onChange={(e) => this.updateCheckbox(e.target.value)}
/>
Society
<br></br>
<div>
<button
className="button"
type="submit"
disabled={!this.state.formValid}
>
Submit
</button>
</div>
</div>
);
}
}
Define count in the state and update it based on the checkbox selection,
state = {
checkbox: "",
checkboxValid: false,
errorMsg: {},
selectedCheckBox: 0
};
Update Logic:-
updateCheckbox = ({ name, checked }) => {
this.setState(
(prev) => ({
checkbox: checked,
selectedCheckBox: checked
? prev.selectedCheckBox + 1
: prev.selectedCheckBox - 1
}),
this.validateCheckbox
);
};
Use the selectedCheckBox count in the state for validation
Completed Code:-
import React from "react";
import "./styles.css";
export default class App extends React.Component {
state = {
checkbox: "",
checkboxValid: false,
errorMsg: {},
selectedCheckBox: 0
};
validateForm = () => {
const { checkboxValid } = this.state;
this.setState({
formValid: checkboxValid
});
};
updateCheckbox = ({ name, checked }) => {
this.setState(
(prev) => ({
checkbox: checked,
selectedCheckBox: checked
? prev.selectedCheckBox + 1
: prev.selectedCheckBox - 1
}),
this.validateCheckbox
);
};
validateCheckbox = () => {
const { checkbox } = this.state;
let checkboxValid = true;
let errorMsg = { ...this.state.errorMsg };
if (this.state.selectedCheckBox < 2) {
checkboxValid = false;
errorMsg.checkbox = "Please select atleast 2 checkbox";
}
this.setState({ checkboxValid, errorMsg }, this.validateForm);
};
render() {
return (
<div>
<label htmlFor="checkbox">checkbox</label>
{/* <ValidationMessage
valid={this.state.checkboxValid}
message={this.state.errorMsg.checkbox}
/> */}
<input
type="checkbox"
name="business"
onChange={(e) => this.updateCheckbox(e.target)}
/>
Sports
<br></br>
<input
type="checkbox"
name="health"
onChange={(e) => this.updateCheckbox(e.target)}
/>
Business
<br></br>
<input
type="checkbox"
name="society"
onChange={(e) => this.updateCheckbox(e.target)}
/>
Health
<br></br>
<input
type="checkbox"
onChange={(e) => this.updateCheckbox(e.target)}
/>
Society
<br></br>
<div>
<button
className="button"
type="submit"
disabled={!this.state.formValid}
>
Submit
</button>
<br />
<b style={{ fontSize: "30px" }}>{this.state.selectedCheckBox}</b>
</div>
</div>
);
}
}
Working Demo - https://codesandbox.io/s/frosty-colden-8hdm4?file=/src/App.js:0-2160
One way to solve this is by having a different state for each checkbox. Set a name for each checkbox so that it can be access by e.target.name
Notice that the name of the input is the same as the state.
state = {
checkbox1: false,
checkbox2: false,
checkboxValid: false,
};
updateCheckbox = (e) => {
this.setState({ e.target.name: e.target.checked });
};
if(this.state.checkbox1 && this.state.checkbox2) {
//both are checked!
}
change input to
<input
name="checkbox1"
type="checkbox"
onChange={this.updateCheckbox}
checked={this.state.checkbox1}
/>
React-Select with Formik is not loading the selected value in select componenet but I'm able to get values on form submission and validation also works with Yup
Here is a codesandbox demo for the same - https://codesandbox.io/s/wild-violet-fr9re
https://codesandbox.io/embed/wild-violet-fr9re?fontsize=14
import React, { Component } from "react";
import { Formik, Form, ErrorMessage } from "formik";
import * as Yup from "yup";
import Select from "react-select";
const debug = true;
class SelectForm extends Component {
constructor(props) {
super(props);
this.state = {
stateList: [],
stateCity: "",
selectedState: "",
citiesToLoad: []
};
}
handleState(opt) {
console.log(opt.value);
let citiesList = [];
Object.keys(this.state.stateCity).forEach(key => {
if (key === opt.value) {
this.state.stateCity[key].map((cityName, j) => {
citiesList.push(cityName);
});
}
});
this.setState({
selectedState: opt.value,
citiesToLoad: citiesList
});
}
handleMyCity(opt) {
console.log(opt.value);
}
componentDidMount() {
let stateLi = [];
fetch(`stateCity.json`)
.then(response => {
console.log(response);
return response.json();
})
.then(data => {
console.log(data);
for (let key in data) {
if (data.hasOwnProperty(key)) {
stateLi.push(key);
}
}
this.setState({ stateCity: data, stateList: stateLi });
})
.catch(err => {
console.log("Error Reading data " + err); // Do something for error here
});
}
render() {
const { selectedState, stateList, citiesToLoad } = this.state;
const newStateList = stateList.map(item => ({ label: item, value: item }));
const newCitiesToLoad = citiesToLoad.map(item => ({
label: item,
value: item
}));
return (
<div id="signupContainer" className="signinup-container">
<h3 className="mb-4"> Sign Up </h3>
<Formik
initialValues={{
state: selectedState,
city: ""
}}
validationSchema={Yup.object().shape({
state: Yup.string().required("Please select state."),
city: Yup.string().required("Please select city.")
})}
onSubmit={(values, { resetForm, setErrors, setSubmitting }) => {
setTimeout(() => {
console.log("Getting form values - ", values);
setSubmitting(false);
}, 500);
}}
enableReinitialize={true}
>
{props => {
const {
values,
touched,
dirty,
errors,
isSubmitting,
handleChange,
setFieldValue,
setFieldTouched
} = props;
return (
<Form id="signUpForm" className="signinupForm" noValidate>
<div className="form-group">
<label htmlFor="state" className="form-label">
State
</label>
<Select
name="state"
id="state"
onBlur={() => setFieldTouched("state", true)}
value={values.state}
onChange={(opt, e) => {
this.handleState(opt);
handleChange(e);
setFieldValue("state", opt.value);
}}
options={newStateList}
error={errors.state}
touched={touched.state}
/>
</div>
<div className="form-group">
<label htmlFor="city" className="form-label">
City
</label>
<Select
name="city"
id="city"
onBlur={() => setFieldTouched("city", true)}
value={values.city}
onChange={(opt, e) => {
this.handleMyCity(opt);
setFieldValue("city", opt.value);
}}
options={newCitiesToLoad}
/>
</div>
{isSubmitting ? (
<span className="loader-gif">
<img src={loading} alt="Loading..." />
</span>
) : null}
<button
type="submit"
className="btn btn-filled"
disabled={!dirty || isSubmitting}
>
Submit
</button>
{/*Submit */}
</Form>
);
}}
</Formik>
</div>
);
}
}
export default SelectForm;
Upon selecting any value from the selecet dropdown, my selected value should appear in select box
You are setting the field value on onchange of select setFieldValue("state", opt.value); so you don't need to set value for the <Select>:
<Select
name="state"
id="state"
onBlur={() => setFieldTouched("state", true)}
onChange={(opt, e) => {
this.handleState(opt);
handleChange(e);
setFieldValue("state", opt.value);
}}
options={newStateList}
error={errors.state}
touched={touched.state}
/>
change for the both <Select>
react-select accepts an object as a value so you need to pass an object of
let object = {
"label": "Andhra Pradesh",
"value": "Andhra Pradesh"
}
bypassing an object in value the selected value appears in the select box
Here is a codesandbox demo https://codesandbox.io/s/floral-fire-8txrt
so updated code is
import React, { Component } from "react";
import { Formik, Form, ErrorMessage } from "formik";
import * as Yup from "yup";
import Select from "react-select";
const debug = true;
class SelectForm extends Component {
constructor(props) {
super(props);
this.state = {
stateList: [],
stateCity: "",
selectedState: "",
citiesToLoad: []
};
}
handleState(opt) {
console.log(opt.value);
let citiesList = [];
Object.keys(this.state.stateCity).forEach(key => {
if (key === opt.value) {
this.state.stateCity[key].map((cityName, j) => {
citiesList.push(cityName);
});
}
});
this.setState({
selectedState: opt,
citiesToLoad: citiesList
});
}
handleMyCity(opt) {
console.log(opt.value);
}
componentDidMount() {
let stateLi = [];
fetch(`stateCity.json`)
.then(response => {
console.log(response);
return response.json();
})
.then(data => {
console.log(data);
for (let key in data) {
if (data.hasOwnProperty(key)) {
stateLi.push(key);
}
}
this.setState({ stateCity: data, stateList: stateLi });
})
.catch(err => {
console.log("Error Reading data " + err); // Do something for error here
});
}
render() {
const { selectedState, stateList, citiesToLoad } = this.state;
const newStateList = stateList.map(item => ({ label: item, value: item }));
const newCitiesToLoad = citiesToLoad.map(item => ({
label: item,
value: item
}));
return (
<div id="signupContainer" className="signinup-container">
<h3 className="mb-4"> Sign Up </h3>
<Formik
initialValues={{
state: selectedState,
city: ""
}}
validationSchema={Yup.object().shape({
state: Yup.string().required("Please select state."),
city: Yup.string().required("Please select city.")
})}
onSubmit={(values, { resetForm, setErrors, setSubmitting }) => {
setTimeout(() => {
console.log("Getting form values - ", values);
setSubmitting(false);
}, 500);
}}
enableReinitialize={true}
>
{props => {
const {
values,
touched,
dirty,
errors,
isSubmitting,
handleChange,
setFieldValue,
setFieldTouched
} = props;
return (
<Form id="signUpForm" className="signinupForm" noValidate>
<div className="form-group">
<label htmlFor="state" className="form-label">
State
</label>
<Select
name="state"
id="state"
onBlur={() => setFieldTouched("state", true)}
value={values.state}
onChange={(opt, e) => {
this.handleState(opt);
handleChange(e);
setFieldValue("state", opt);
}}
options={newStateList}
error={errors.state}
touched={touched.state}
/>
</div>
<div className="form-group">
<label htmlFor="city" className="form-label">
City
</label>
<Select
name="city"
id="city"
onBlur={() => setFieldTouched("city", true)}
value={values.city}
onChange={(opt, e) => {
this.handleMyCity(opt);
setFieldValue("city", opt);
}}
options={newCitiesToLoad}
/>
</div>
{isSubmitting ? (
<span className="loader-gif">
<img src={loading} alt="Loading..." />
</span>
) : null}
<button
type="submit"
className="btn btn-filled"
disabled={!dirty || isSubmitting}
>
Submit
</button>
{/*Submit */}
</Form>
);
}}
</Formik>
</div>
);
}
}
export default SelectForm;
How would I push these values to the employment history state? I am getting the read out in form but not to the initial state?
Should I use formSelector to push values to state? This has been a nightmare for me, as it is 99% finished except for getting everything to push to state. So that I can push everything where it needs to go once form is complete.
class EmploymentHistoryQuestion extends Component {
static propTypes = {
roles: PropTypes.array,
handleSubmit: PropTypes.func,
driverProfile: PropTypes.object,
fields: PropTypes.object,
whenUserCompletedQuestion: PropTypes.func.isRequired,
};
state = {
employmentHistory: {
employerName: '',
role: '',
startDate: '',
endDate: '',
description: '',
currentPosition: false,
},
};
handleChange = (field) => (response, value) => {
const { employmentHistory } = this.state;
const { whenUserCompletedQuestion } = this.props;
console.log(employmentHistory);
this.setState({
employmentHistory,
}, () => {
if (field === 'startDate' || field === 'endDate') {
whenUserCompletedQuestion({
[field]: response.value,
});
} else if (field === 'role') {
whenUserCompletedQuestion({
[field]: value,
});
} else if (field === 'employerName' || field === 'description') {
whenUserCompletedQuestion({
[field]: response.target.value,
});
}
});
};
render() {
const { fields, roles, handleSubmit, driverProfile, ...more } = this.props;
return (
<div className={bem.el('form')}>
<form onSubmit={handleSubmit}>
<Field
name={'employerName'}
label={translations.translate('driverProfile', 'employerName')}
component={renderTextField}
onChange={this.handleChange('employerName')}
fullWidth
required
/>
<div style={{ minHeight: '120px' }}>
<InputField
question={'startDate'}
onChange={this.handleChange('startDate')}
required
/>
</div>
<div style={{ minHeight: '120px' }}>
<InputField
question={'endDate'}
onChange={this.handleChange('endDate')}
required
/>
</div>
<Field
name={'role'}
component={renderSelectField}
value={this.state.role}
onChange={this.handleChange('role')}
required
fullWidth
>
{roles.map((role, index) =>
(<MenuItem
value={role}
key={index}
primaryText={translations.translate('vehicleTypes', role)}
/>)
)}
</Field>
<Field
name={'description'}
label={translations.translate('editJob', 'description')}
component={renderTextArea}
onChange={this.handleChange('description')}
fullWidth
/>
</form>
</div>
);
}
}
export default reduxForm({
form: 'employmentHistory',
})(EmploymentHistoryQuestion);