Form submit for react-country-region-selector using Redux - javascript

I have to implement country-region selection (dropdown) in ReactJS.
I have used react-country-region-selector and created a component CountryRegion which has the CountryDropDown and RegionDropDown implementation.
My app uses redux-form. I need to pass the user selected values for counry and region to the parent form in which I am using the CountryRegion component.
I tried making using of redux-form "Fields" but it throws this error:
Uncaught TypeError: Cannot read property 'onChange' of undefined.
This is the CountryRegion.jsx -
import React, { Component } from 'react';
import 'react-select/dist/react-select.css'
import 'react-virtualized/styles.css'
import 'react-virtualized-select/styles.css'
import { CountryDropdown, RegionDropdown } from 'react-country-region-selector';
class CountryRegion extends Component {
constructor (props) {
super(props);
this.state = { country: '', region: '' };
}
selectCountry (val) {
this.setState({ country: val });
}
selectRegion (value) {
this.setState({ region: value });
}
render () {
const {input, name, className} = this.props;
const {country, region } = this.state;
return (
<div>
<label className={"col-form-label"}>Work Country</label>
<CountryDropdown class="form-control" name="COUNTRY"
value={country}
valueType="short" priorityOptions={["US","CA"]} showDefaultOption={false}
onChange={(val) => {input.onChange(this.selectCountry(val)); }}/>
<label className={"col-form-label"}>Work State / Province</label>
<RegionDropdown class="form-control" name="STATE"
country={this.state.country}
value={this.state.region}
valueType="short"
countryValueType="short"
showDefaultOption={false}
onChange={(value) => {input.onChange(this.selectRegion(value));}}/>
</div>
);
}
}
export default CountryRegion;
This is how I am referring the CountryRegion code in parent form:
{<Fields names={[COUNTRY,STATE]} component={CountryRegion}/>}
How do I bind the value from the two drop downs to form attribute or Fields in redux form every time user selects or changes the dropdown values?

I used props to bind the properties for form submission. The code is below for onchange method-
<RegionDropdown className="form-control" name="STATE"
country={country}
value={region}
valueType="short"
countryValueType="short"
showDefaultOption={true}
defaultOptionLabel={"Select State"}
onChange={(value) =>
{this.props.state.input.onChange(this.selectRegion(value));}}/>

Related

How to grab data from object in child component and pass it to a method in the parent component in React

I have been looking everywhere but I have not found a way. I only found how to pass data from parent to child in React. So that is why I am asking this question. I have a parent component which is a form and it takes input from whatever the user inputs. However, there is a field that it doesnt have access to in the parent component and is accessed in the method of the child component. That field is "priorityLevel" in the parent component which I have set to null(just waiting on the child component to provide that info). In the child component, I capture that "priorityLevel" data using a ref and storing that data in the "handleChange" method. It does log out the info that is selected. However, I need to pass that data to the parent component so that the parent component can see it and use it. Please see my code below. Thanks!
// Parent Component(TodoForm.js)
import React from "react";
import PrioritySelector from "./PrioritySelector";
import { connect } from "react-redux";
class TodoForm extends React.Component {
/*submit handler to grab input from the input references and store them
in the "todoData" object. Then dispatches an action type and payload
capturing the data. Then clears the input*/
handleSubmit=(e)=> {
e.preventDefault();
const todoTitle = this.getTodoTitle.value;
const description = this.getDescription.value;
const priorityLevel = null;
const todoData = {
id: Math.floor(Math.random()*1000),
todoTitle,
description,
priorityLevel,
editing: false
}
this.props.dispatch({type:"ADD_TODO", todoData })
this.getTodoTitle.value = "";
this.getDescription.value = "";
}
render() {
console.log(this.props)
return(
<div>
<form onSubmit={this.handleSubmit}>
<input type="text" ref={(input)=> this.getTodoTitle=input} placeholder="Enter Todo" required/>
<input type="text" ref={(input)=> this.getDescription=input} placeholder="Enter Description" required/>
<PrioritySelector />
<button>Add Todo</button>
</form>
</div>
)
}
}
export default connect()(TodoForm);
// Child Component(PrioritySelector.js)
import React from "react";
import $ from "jquery";
import { connect } from "react-redux";
class PrioritySelector extends React.Component {
componentDidMount() {
$("#selector").show();
}
handleSelect =(e)=> {
const priorityLevel = this.getPriorityLevel.value;
const priorityLevelData = {
priorityLevel
}
console.log(priorityLevelData)
}
render() {
console.log(this.props)
return(
<div>
<div className="input-field col s12">
<select onChange={this.handleSelect} ref={(option)=> this.getPriorityLevel = option} id="selector">
<option disabled selected>Choose your option</option>
<option value="1">Low</option>
<option value="2">Medium</option>
<option value="3">High</option>
</select>
</div>
</div>
)
}
}
const mapStateToProps=(state)=> {
return {
priorityLevel: state
}
}
export default connect(mapStateToProps)(PrioritySelector);
In TodoForm:
state = {
priorityLevel: {},
}
and
<PrioritySelector onSelect={priorityLevel => this.setState({ priorityLevel })} />
In PrioritySelector:
handleSelect =(e)=> {
const priorityLevel = this.getPriorityLevel.value;
const priorityLevelData = {
priorityLevel
}
this.props.onSelect(priorityLevelData)
}

React onChange doesn't fire on text input when formatter.js is used

I'm using a formatter.js to format some input box. I want to connect this formatter to my react app so I've write a simple module but onChange function doesn't fire. When I remove the formatter library the input box works as planned. But I want to format the inputs and also use the values inside my React application.
After a brief search over the internet I've came across with this question;React: trigger onChange if input value is changing by state? but I'm not sure how can I apply that solution to my application.
ReactMaskedInput.js
import React, { Component } from 'react'
class ReactMaskedInput extends Component {
constructor(props) {
super(props)
this.onChangeHandler = this.onChangeHandler.bind(this)
this.state = {
value: ""
}
}
onChangeHandler(event) {
this.setState({
value: event.target.value
})
alert(this.state.value)
}
componentDidMount() {
let dataMask = this.props.dataMask
window.$(`#${this.props.id}`).formatter({
pattern: dataMask
});
}
render() {
return (
<div >
<h3>
<b>{this.props.header}</b>
</h3>
<input
id={this.props.id}
type="text"
placeholder={this.props.placeHolder}
className="form-control"
onChange={event => this.onChangeHandler(event)}
name={this.props.name}
>
</input>
</div>
)
}
}
export default ReactMaskedInput
Index.js
ReactDOM.render(<ReactMaskedInput
id="myMaskedInput"
header="Masked Input Phone"
onChange={(event) => { deneme(event); }}
dataMask={"({{999}}) {{999}}-{{9999}}"}
name="maskedInput1"
placeHolder="Please enter a valid phone number"
validationInitiatorNr={10}
// onChange={(phoneNr) => validatePhone(phoneNr)}
/>, document.getElementById('myFormattedInput'))
Fix your onChangeHandler code
You have to call the 'onChange' handler you passed as an attribute in code of your ReactMaskedInput class explicitly. I guess you are assuming that it would get called automatically. Note that ReactMaskedInput is a component you created, and not an HTML tag 'onChange' of which gets called by React.
onChangeHandler(event) {
this.setState(() => ({
value: event.target.value
}), () => {
this.props.onChange(event) // Call 'onChange' here
alert(this.state.value) // alert should be inside setState callback
})
}

Can't get selected value from dropdown

I have a form that contains an input and a dropdown that's filled with elements from an API. But I'm having a probem, whenever I submit the form It only passes the value from the input and not the dropdown.
It's weird because when I click inspect element on the form it show that each option has a value and a label.
My code is simple, I have a form that has an input and a dropdown, I get an int from the input and a value from the dropdown and it creates an element via POST request, but that happens in the background since I only pass the parameters here.
I am using the Redux Form library for my form controls
here's my code:
import React from 'react';
import {reduxForm, Field} from 'redux-form';
import {Input} from 'reactstrap';
import {connect} from 'react-redux';
import { renderField } from '../form';
import {ticketAdd} from "../actions/actions";
import {Message} from "./Message";
const mapDispatchToProps = {
ticketAdd
};
class AmendeForm extends React.Component {
onSubmit(values) {
const { ticketAdd, parkingId } = this.props;
return ticketAdd(parseInt(values.matricule),parkingId,parseInt(values.montant));
}
render() {
const { handleSubmit, submitting, voitureList } = this.props;
console.log(voitureList);
if (null === voitureList) {
return (<Message message="Pas de voitures"/>);
}
return (
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<Input type="select" name="matricule" id="exampleSelect" label="Matricule">
{
voitureList.map(voiture => {
return (
<option value={voiture.id} key={voiture.id}>{voiture.matricule}</option>
);
})
}
</Input>
<Field name="montant" type="number" label="Montant" component={renderField}/>
<button type="submit" className="btn btn-primary btn-big btn-block" disabled={submitting}>Ajouter ticket</button>
</form>
)
}
}
export default reduxForm({
form: 'AmendeForm'
})(connect(null, mapDispatchToProps)(AmendeForm))
It's because your dropdown form field isn't wrapped by redux-form <Field /> component.
You have to create a custom dropdown Component (let's name it <Dropdown />) and later pass it to the Field as follows:
<Field name='matricule' component={Dropdown} />
Keep in mind that in your <Dropdown /> component, you have to adapt the props passed down by <Field /> with your custom Dropdown props. For example, <Field /> will pass down input prop, which includes itself onChange, onBlur and other handlers, these should be passed down to your custom Dropdown too.
Here's a basic example how to create such a custom Dropdown component:
const Dropdown = ({ input, label, options }) => (
<div>
<label htmlFor={label}>{label}</label>
<select {...input}>
<option>Select</option>
{ options.map( o => (
<option key={o.id} value={o.id}>{o.label}</option>
)
)}
</select>
</div>
)
Usage
const options = [
{ id: 1, label: 'Example label 1' },
{ id: 2, label: 'Example label 2' }
]
<Field name='marticule' component={Dropdown} options={options} />
For advanced use-cases, please refer to the docs.
Also, you're using reactstrap, and here's a discussion of how to create such custom Dropdown component, which is adapted with redux-form.

Redux Form - How to handle default selections on async select fields?

I have 3 components: UserProfile, CountryControl and StateControl. I have it setup so the CountryControl and StateControl are responsible for issuing their own ajax requests for data (via actions). When Country changes, the States should be fetched. UserProfile passes a callback to CountryControl so I can tell States to update when Country changes.
Now this works, but relies on the onChange event of the Country selection input to determine when to load States. This is fine except on initial load with an existing UserProfile. Since there is no onChange event fired when Redux Form injects initial values to the Field component I don't have a list of States until I change Country and change back.
Is there a way to either trigger the onChange event when Redux sets the form values - or perhaps when countries are loaded to trigger the onChange?
The parent UserProfile component:
class UserProfile extends Component {
componentDidMount() {
service.getUserIdentity(userId, (userProfile) => {
this.props.initialize(userProfile);
});
onCountryChanged(e, input) {
// dispatch a call to update states when selection changes
input.onChange(e);
this.props.dispatch(getStates(e.target.value));
}
render() {
<div>
<Field
id = "name"
label = "Name"
placeholder = "John Doe"
name = "name"
component={this.renderField}
/>
<StateControl />
<CountryControl onSelectChanged={this.onCountryChanged} />
</div>
}
renderField(field) {
return (
<div className="control-group">
<div className="control-label">{field.label}</div>
<div className="controls">
<input {...field.input} type={field.type} placeholder={field.placeholder} className="form-control"/>
</div>
</div>
);
}
}
function mapStateToProps(state, ownProps) {
return { userProfile: state.userProfile };
}
export default reduxForm({
validate: validate,
form: 'UserProfile'
})(
connect(mapStateToProps, null)(UserProfile)
);
The child country control:
class CountryControl extends Component {
componentDidMount() {
this.props.getCountries();
}
render() {
const { countries } = this.props;
return (
<Field
label = "Country"
name = "country"
component = {this.renderSelect}
data = {countries}
onCustomChange = {this.props.onSelectChanged.bind(this)}
>
</Field>
);
}
renderSelect(field) {
return (
<div className="control-group">
<div className="control-label">{field.label}</div>
<div className="controls">
<select
className="form-control"
onChange={e => {field.onCustomChange(e, field.input) }}
value={field.input.value}
>
<option value=""></option>
{Array.isArray(field.data) ? field.data.map(option =>
<option
value={option.countryCode}
key={option.countryCode}>{option.name}</option>) : ''}
</select>
</div>
</div>
);
}
}
function mapStateToProps(state, ownProps) {
return { countries: state.countries };
}
export default reduxForm({
form: 'UserProfile'
})(
connect(mapStateToProps, { getCountries })(CountryControl)
);
I have something that works, but I bet there's a better way.
In the CountryControl I can manually fire a change event on the select box after the getCountries ajax call completes.
componentDidMount() {
// load data and bind
this.props.getCountries().then(() => {
// trigger the onChange event
var element = document.getElementsByName('country')[0];
if ("createEvent" in document) {
var evt = document.createEvent("HTMLEvents");
evt.initEvent("change", true, true);
element.dispatchEvent(evt);
}
else{
element.fireEvent("onchange");
}
});
}
I'd be inclined to wrap <UserProfile /> in a component which on mount, fetches the initial values.
With this approach, you'd even be able to fetch the user => country => state info necessary all before the for even renders so you don't need to manually trigger an onChange.

Inputs in child component won't update when prop is changing in parent component

I have a shopping list app that is divided to two components as follows:
I implemented those two components as: ShoppingList and:ItemDetails
There is another component: ListItem that represents one item row (with edit and delete buttons).
ShoppinList maps over an array of ListItems.
My App component fetches an initial items array and sends it to ShoppingList.
Each time a click is made on the edit icon in a specific item row I set selectedItem object in my app component and render the ItemDetails component, passing it the selectedItem like so:
toggleDetailsPanel = (itemId) => (e) => {
this.setState((prevState, props) => {
return {
selectedItem: (prevState.selectedItem && prevState.selectedItem.id === itemId) ? null : this.findItemById(itemId),
};
});
};
And in the App render function I render it like that:
<div className={styles.details_outer_container}>
{this.state.selectedItem ? <ItemDetails handleSubmit={this.saveDetails} item={this.state.selectedItem}/> : null}
</div>
Whenever a click is made on the save button I run a function on the app component that updates the item in the items array (saveDetails).
Now I expected the ItemDetails component to render with new values each time I click on a different edit icon in a different item row, but the inputs values won't change, only the title is rendering.
I tried all solutions that I found, involving defaultValue, or setting value with getValue() function, or setting a dynamic key on the inputs, but nothing really helps.
This is my ItemDetails file:
import React from 'react';
import PropTypes from 'prop-types';
import { Grid, Row, Col, input, Button } from 'react-bootstrap';
import styles from './styles.css';
export default class ProductDetails extends React.Component {
static propTypes = {
handleSubmit: PropTypes.func.isRequired,
item: PropTypes.any.isRequired,
};
state = {
id: this.props.item.id,
name: this.props.item.name,
quantity: this.props.item.quantity,
price: this.props.item.price,
description: this.props.item.description,
};
// Set appropriate property in state by input name
handleInputChange = (e) => {
this.setState({
[e.target.name]: e.target.value,
});
};
// Submit changed item to parent component
handleDetailsSubmit = (e) => {
this.props.handleSubmit(this.state);
e.preventDefault();
};
render() {
const item = this.props.item;
const itemName = item.name.toUpperCase() || '';
return (
<div className={styles.details_container}>
<div className="sub_header">
<span>{`${itemName} DETAILS`}</span>
</div>
<form className={styles.form_style}>
<p>
<label>{'Quantity'}</label>
<input type="text" ref="quantity" name="quantity" value={this.state.quantity} onChange={this.handleInputChange} />
</p>
<p>
<label>{'Price'}</label>
<input type="text" ref="price" name="price" value={this.state.price} onChange={this.handleInputChange}/>
</p>
<p>
<label>{'Description'}</label>
<textarea rows={2} ref="description" name="description" value={this.state.description} onChange={this.handleInputChange}/>
</p>
<div className={styles.button_div}>
<Button onClick={this.handleDetailsSubmit} bsStyle="primary" bsSize="small">
{'Save'}
</Button>
</div>
</form>
</`enter code here`div>
);
}
}
I understand this is React's way of handling forms but really don't know how to solve it.
I would really appreciate any help : )
The ProductDetails component only gets its initial values from item. From that point it is all maintained in state. So you need to reset the state when item changes.
Try adding something like this:
componentWillReceiveProps( newProps ) {
if ( this.props.item !== newProps.item ) {
this.setState( {
id: newProps.item.id,
name: newProps.item.name,
quantity: newProps.item.quantity,
price: newProps.item.price,
description: newProps.item.description,
} )
}
}

Categories

Resources