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.
Related
Hello friends I'm pretty new to React JS so I want to ask you this:
I have a Parent and Child component. In Child component I have inputs (name,email etc) and in Parent component I have Button to save the input data in database.
My question is: How to validate inputs(i want them to be required) so the button can NOT call saveData function(to save in database) if the inputs are empty.
Here is Parent Component:
class Parent extends Component{
saveData = (e) => {
//some code
}
render() {
return (
<div>
<Child/>
<Button color="primary" onClick={this.saveData}>Submit</Button>
</div>
);
}
}
And here is Child Component:
class Child extends React.Component {
onInputChange = (e) => {
e.preventDefault();
this.props.onInputChange(e.target.name, e.target.value);
};
render() {
return (
<FormGroup>
<Input name="email" onChange={this.onInputChange}/>
</FormGroup>
);
}
}
I cant see the implementation of your onInputChange function but looks like its coming from parent component.
So, if you have onInputChange in your parent component then, you can have a disable state that is passed to your Button component and you need to set that disable state when onInputChange is called i.e
onInputChange = (name, value) => {
if(value === ''){
this.setState({disable: true});
}
else{
this.setState({
name: value;
disable:false
})
}
}
And pass it to your button i.e.
<Child />
<Button color="primary" disabled={disable} onClick={this.saveData}>
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
})
}
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));}}/>
I'm trying to create an input form that stores information in a component's state variable, then outputs that variable on the screen. I read the docs on controlled components, and that's what I'm attempting here.
My main issue here is when I click submit, the correct text appears on the screen, but then the entire page refreshes and I'm not sure why. From what I've read online, refs appear to be a solution, but my understanding is that I can use either that, or controlled components.
class InputField extends React.Component {
constructor(props) {
super(props);
this.state = {
itemName: "",
storedItemName: "",
};
this.handleNameChange = this.handleNameChange.bind(this);
this.afterSubmission = this.afterSubmission.bind(this);
}
handleNameChange(event) {
this.setState({
itemName: event.target.value
});
}
afterSubmission(event) {
let name = this.state.itemName;
this.setState ({
storedItemName:this.state.itemName
}, function() {
alert(this.state.storedItemName); // Shows the right value!
});
}
render() {
return(
<div>
<form onSubmit = {this.afterSubmission}>
<label> Item Name:
<input
type = "text"
name = "itemName"
value = {this.state.itemName}
onChange = {this.handleNameChange}
/></label>
<input type = "submit" value = "Submit" />
</form>
<div className = "itemList">
<p>Hi</p>
{this.state.storedItemName}
</div>
</div>
);
}
Just call event.preventDefault method to prevent default behavior of form
afterSubmission(event) {
event.preventDefault();
let name = this.state.itemName;
this.setState ({
storedItemName:this.state.itemName
}, function() {
alert(this.state.storedItemName); // Shows the right value!
}
}
Prevent the default behaviour:
afterSubmission(event) {
event.preventDefault();
let name = this.state.itemName;
this.setState ({
storedItemName:this.state.itemName
}, function() {
alert(this.state.storedItemName); // Shows the right value!
});
}
To prevent basic React form submit from refreshing the entire page, we call e.preventDefault. in the submit event handler.
For instance, we write:
import React from "react";
export default function App() {
const onSubmit = (e) => {
e.preventDefault();
console.log("refresh prevented");
};
return (
<div>
<form onSubmit={onSubmit}>
<button type="submit">submit</button>
</form>
</div>
);
}
to create the onSubmit function that’s set as the value of the onSubmit prop.
In the function, we call e.preventDefault to prevent the default submission behavior, which is to do server side form submission which refreshes the whole page.
We have a submit button in the form to do the form submission.
Therefore, instead of seeing the page refresh when we click submit, we should see the 'refresh prevented' string logged in the console instead.
There are 3 ways you can do this:
1st WAY
By using event.preventDefault(); function
When we use onSubmit() event for form submission the default
behaviour of this event is to refresh the browser and render a new
html page.
To prevent this default behaviour of page refresh for onSubmit event
we put event.preventDefault(); inside the function we are calling for
onSubmit event.
InputField.js
class InputField extends React.Component {
state = {
itemName: "",
storedItemName: "",
}
handleNameChange = (event) => {
const { name,value } = event.target;
//we can't write this.setState({name:value}) this will set name as the key we need value of the name which is itemName
this.setState({
[name] : value //itemName:"the text we will enter"
});
}
afterSubmission = (event) => {
event.preventDefault();
this.setState ({
storedItemName:this.state.itemName
}, function() {
alert(this.state.storedItemName);
});
}
render() {
return(
<div>
<form onSubmit = {this.afterSubmission}>
<label> Item Name:
<input
onChange = {this.handleNameChange}
type = "text"
name = "itemName"
value = {this.state.itemName}
/>
</label>
<input type = "submit" value = "Submit" />
</form>
<div className = "itemList">
<p>Hi</p>
{this.state.storedItemName}
</div>
</div>
);
}
}
export default InputField;
2nd WAY
By removing onSubmit event from form element
Next is change the input type of submit to input type button
and add an onClick event to it
InputField.js
class InputField extends React.Component {
state = {
itemName: "",
storedItemName: "",
}
handleNameChange = (event) => {
console.log(this);
const { name,value } = event.target;
this.setState({
[name] : value
});
}
afterSubmission = () => {
this.setState ({
storedItemName:this.state.itemName
}, function() {
alert(this.state.storedItemName);
});
}
render() {
return(
<div>
<form>
<label> Item Name:
<input
onChange = {this.handleNameChange}
type = "text"
name = "itemName"
value = {this.state.itemName}
/>
</label>
<input
type = "button"
onClick={this.afterSubmission}
value = "Submit"
/>
</form>
<div className = "itemList">
<p>Hi</p>
{this.state.storedItemName}
</div>
</div>
);
}
}
export default InputField;
3rd WAY
By using react form hook (https://www.react-hook-form.com/)
As per docs we have to install the npm module in our development server
npm install react-hook-form
InputField.js
import InputFieldForm from 'location specified';
class InputField extends React.Component {
render() {
return(
<div>
<InputFieldForm />
</div>
);
}
}
export default InputField;
InputFieldForm.js
import React from "react";
import { useForm } from "react-hook-form";
const InputFieldForm = () =>{
const onSubmit = (formData) =>{
alert(JSON.stringify(formData));
console.log(formData);
}
const { register, handleSubmit } = useForm();
return(
<form onSubmit = {handleSubmit(onSubmit)}>
<label> Item Name:
<input
{...register('itemName')}
type = "text"
name = "itemName"
/>
</label>
<input type = "submit" value = "Submit" />
</form>
)
}
export default InputFieldForm;
The InputField & Button are custom components that go into a form to create a form. My issue is how do I send the data back up to form so that on button click, I can fire ajax on the form with data (username & password):
export default auth.authApi(
class SignUpViaEmail extends Component{
constructor(props){
super(props);
this.state = {
email : "",
password : ""
};
this.storeEmail = this.storeEmail.bind( this );
this.storePassword = this.storePassword.bind( this );
}
storeEmail(e){
this.setState({ email : e.target.value });
}
storePassword(e){
this.setState({ password : e.target.value });
}
handleSignUp(){
this.props.handleSignUp(this.state);
}
render(){
return(
<div className="pageContainer">
<form action="" method="post">
<InputField labelClass = "label"
labelText = "Username"
inputId = "signUp_username"
inputType = "email"
inputPlaceholder = "registered email"
inputClass = "input" />
<Button btnClass = "btnClass"
btnLabel = "Submit"
onClickEvent = { handleSignUp } />
</form>
</div>
);
}
}
);
Or Is it not recommended & I should not create custom child components within the form?
child component => InputField
import React,
{ Component } from "react";
export class InputField extends Component{
constructor( props ){
super( props );
this.state = {
value : ""
};
this.onUserInput = this.onUserInput.bind( this );
}
onUserInput( e ){
this.setState({ value : e.target.value });
this.props.storeInParentState({[ this.props.inputType ] : e.target.value });
}
render(){
return <div className = "">
<label htmlFor = {this.props.inputId}
className = {this.props.labelClass}>
{this.props.labelText}
</label>
<input id = {this.props.inputId}
type = {this.props.inputType}
onChange = {this.onUserInput} />
<span className = {this.props.validationClass}>
{ this.props.validationNotice }
</span>
</div>;
}
}
Error : I get the error e.target is undefined on the parent storeEmail func.
React's one-way data-binding model means that child components cannot send back values to parent components unless explicitly allowed to do so. The React way of doing this is to pass down a callback to the child component (see Facebook's "Forms" guide).
class Parent extends Component {
constructor() {
this.state = {
value: ''
};
}
//...
handleChangeValue = event => this.setState({value: event.target.value});
//...
render() {
return (
<Child
value={this.state.value}
onChangeValue={this.handleChangeValue}
/>
);
}
}
class Child extends Component {
//...
render() {
return (
<input
type="text"
value={this.props.value}
onChange={this.props.onChangeValue}
/>
);
}
}
Take note that the parent component handles the state, while the child component only handles displaying. Facebook's "Lifting State Up" guide is a good resource for learning how to do this.
This way, all data lives within the parent component (in state), and child components are only given a way to update that data (callbacks passed down as props). Now your problem is resolved: your parent component has access to all the data it needs (since the data is stored in state), but your child components are in charge of binding the data to their own individual elements, such as <input> tags.
Addendum
In response to this comment:
What if we render a list of the child component? Using this single source of truth in Lifting state up technique will let the parent controls all the state of all the child inputs right? So how can we access each of the value input in the child component to (which is rendered as list) from the parent component?
For this case, you may map a child component for each element in the list. For example:
class Parent extends Component {
//...
handleChangeListValue = index => event => {
this.setState({
list: this.state.list
.map((element, i) => i === index ? event.target.value : element)
});
}
//...
render() {
return this.state.list.map((element, i) => (
<Child
value={element}
onChangeValue={this.handleChangeListValue(i)}
/>
));
P.S. Disclaimer: above code examples are only for illustrative purposes of the concept in question (Lifting State Up), and reflect the state of React code at the time of answering. Other questions about the code such as immutable vs mutable array updates, static vs dynamically generated functions, stateful vs pure components, and class-based vs hooks-based stateful components are better off asked as a separate question altogether.
React class component
Parent.js
import React, { Component } from 'react';
import Child from './child'
class Parent extends Component {
state = {
value: ''
}
onChangeValueHandler = (val) => {
this.setState({ value: val.target.value })
}
render() {
const { value } = this.state;
return (
<div>
<p> the value is : {value} </p>
<Child value={value} onChangeValue={this.onChangeValueHandler} />
</div>
);
}
}
export default Parent;
Child.js
import React, { Component } from 'react';
class Child extends Component {
render() {
const { value , onChangeValue } = this.props;
return (
<div>
<input type="text" value={value} onChange={onChangeValue}/>
</div>
);
}
}
export default Child;
React hooks
Parent.js
import { useState } from "react";
import Child from "./child";
export default function Parent() {
const [value, changeValue] = useState("");
return (
<div>
<h1>{value}</h1>
<Child inputValue={value} onInputValueChange={changeValue} />
</div>
);
}
Child.js
export default function Child(props) {
return (
<div>
<input
type="text"
value={props.inputValue}
onChange={(e) => props.onInputValueChange(e.target.value)}/>
</div>
);
}
Parent.js
import SearchBar from "./components/SearchBar";
function App() {
const handleSubmit = (term) => {
//Log user input
console.log(term);
};
return (
<div>
<SearchBar onPressingEnter={handleSubmit} />
</div>
);
}
export default App;
Child.js
import { useState } from "react";
function SearchBar({ onPressingEnter }) {
const [UserSearch, setname] = useState("[]");
/* The handleChange() function to set a new state for input */
const handleChange = (e) => {
setname(e.target.value);
};
const onHandleSubmit = (event) => {
//prevent form from making a http request
event.preventDefault();
onPressingEnter(UserSearch);
};
return (
<div>
<form onSubmit={onHandleSubmit}>
<input
type="search"
id="mySearch"
value={UserSearch}
onChange={handleChange}
name="q"
placeholder="Search the site…"
required
/>
</form>
</div>
);
}
export default SearchBar;
You can add a "ref name" in your InputField so you can call some function from it, like:
<InputField
ref="userInput"
labelClass = "label"
labelText = "Username"
inputId = "signUp_username"
inputType = "email"
inputPlaceholder = "registered email"
inputClass = "input" />
So you can access it using refs:
this.refs.userInput.getUsernamePassword();
Where getUsernamePassword function would be inside the InputField component, and with the return you can set the state and call your props.handleSignUp