React change initial state based on another field value - reactjs

How can I change the initial state value of a field based on the input value of another field?
Here's my code:
import React from "react";
export class someComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
grade: "",
tuition: changeStateHere
};
}
render() {
<div>
<form onSubmit={this.someFunction.bind(this)}>
<select name="grade" onChange={this.onChange} value={this.state.grade}>
<option value="">Class</option>
<option value="Grade One">Grade One</option>
<option value="Grade Two">Grade Two</option>
<option value="Grade Three">Grade Three</option>
<option value="Grade Four">Grade Four</option>
<option value="Grade Five">Grade Five</option>
<option value="Grade Six">Grade Six</option>
</select>
<input
type="text"
name="tuition"
placeholder="Tuition"
value={this.state.tuition}
onChange={this.onChange}
/>
</form>
</div>;
}
}
I want to set a dynamic initial state value for tuition based on input option on the grade select field.
For example, if a user selects Grade One, the tuition value should be 15000; if Grade Two, the value should be '20000'; etc.
Is there any workaround dynamically changing the initial state value?

Everything inside of the constructor is run before render. You're not going to be able to set a different initial value to the state based on something inside the render method.
Also move away from binding functions in the render method. it would be better to use a class method someFunction = () => {} and then attach it inside the JSX as onSubmit={this.someFunction}
Each time the component re-renders another instance of someFunction is bound which will eventually lead to performance issues in the browser.

You need to handle the grade selection and evaluate the event.target.value of the select element. Based on this you can use a switch to this.setState both Tuition and whether or not the input field is disabled. You should use defaultValue instead of value so that when the user picks and option it sets the input to this.state.tuition
import React from "react";
export class someComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
grade: "",
gradeSelected: false,
tuition: 0,
};
}
handleGradeSelect = event => {
let newState = Object.assign({}, this.state) // duplicate state so you can mutate this copy
switch(event.target.value){ // evaluate the value of the select option
case 'Grade One':
newState.tuition = 15000
case 'Grade Two':
newState.tuition = 20000
// You get the picture
default:
newState.tuition = 0 // needs to have a default if no option
}
newState.gradeSelected = true
this.setState(newState)
}
render() {
<div>
<form onSubmit={this.someFunction.bind(this)}>
<select name="grade" onChange={event => this.handleChange(event)}>
<option disabled>Class</option>
<option value="Grade One">Grade One</option>
<option value="Grade Two">Grade Two</option>
<option value="Grade Three">Grade Three</option>
<option value="Grade Four">Grade Four</option>
<option value="Grade Five">Grade Five</option>
<option value="Grade Six">Grade Six</option>
</select>
<input
type="text"
name="tuition"
placeholder="Tuition"
value={this.state.tuition}
onChange={this.onChange}
disabled={!this.state.gradeSelected}
/>
</form>
</div>;
}
}

Related

ReactJs: Rendering Similar React Components from a list without using same states for each of the element

I have a CustomReactComponent class which holds a list of similar React Components, I then render a single element Component from the list based on some condition.
This Component has <input/> tag.
When I try to render it all the <input/> tags have the same value when User changes only one of them.
How can I prevent this from happening? and have the inputs store differnt values for each instance
Minimal Reproducible Code:
import {Component} from "react";
export default class CustomReactComponent extends Component {
constructor(props) {
super(props);
this.state = {selected: 0};
this.list = [<div><input/></div>, <div><input/></div>, <div><input/></div>, <div><input/></div>];
}
onSelect = (e) => {
this.setState({selected: e.target.value});
}
render() {
let Component = this.list[this.state.selected];
return (
<div>
<select onChange={this.onSelect}>
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<div>
State:{this.state.selected}:
{Component}
</div>
</div>
);
}
}
Edit 1:
As #asynts pointed out we could use key for each component, but keys's check for siblings during rendering, and since here i am rendering only a single component, key would be of no use, it re-initializes the component every time the state selected changes. But this is a start, it atleast dosent treat all the components in this.list as the same thing
Edit 2:
After some tinkering and experimentation I found a work around for this, we can have a Wrap Component which is given a unique id uid as a prop this can then be used during rendering if state selected points to uid of a Wrap we style it to display: initial else to display: none, this is where key's comes handy we can always render all the siblings and give it a key same as uid
Code:
import {Component} from "react";
function Wrap({uid}) {
return <div>uid = {uid} : <input/></div>
}
export default class CustomReactComponent extends Component {
constructor(props) {
super(props);
this.state = {selected: 0};
this.list = [
<Wrap uid={0}/>,
<Wrap uid={1}/>,
<Wrap uid={2}/>,
<Wrap uid={3}/>
];
}
onSelect = (e) => {
this.setState({selected: parseInt(e.target.value)});
}
render() {
return (
<div>
State:
<select onChange={this.onSelect}>
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<div>
{this.list.map((component) =>
<div key={component.props.uid}
style={{display: component.props.uid !== this.state.selected ? 'none' : 'initial'}}>
{component}
</div>)}
</div>
</div>
);
}
}
This is actually a much more interesting problem than I originally thought.
It seems that React simply can't tell that you are rendering a different element.
Ultimately, the JSX syntax is just syntactic sugar for React.createElement which returns an object:
// jsx:
<input />
// js:
React.createElement("input");
// result object:
{
type: "input",
// This is the default.
key: null,
// There is some other stuff in here too.
}
In other words, all the entries in your array are identical as far as React is concerned.
You can resolve this problem by specifying a key attribute in each array entry:
this.list = [
<div key="1"><input/></div>,
<div key="2"><input/></div>,
<div key="3"><input/></div>,
<div key="4"><input/></div>,
];
There is some documentation about this but it's a bit vague.
Here is an in-depth explanation about why keys are necessary if you’re interested in learning more.
TL;DR: React will create a tree of these objects I mentioned and then computes the difference to the previous tree and updates the DOM. In your case, it is not able to find a difference and therefore needs the key attribute to help.
We could use key for each component, but key's check for siblings during rendering, and here only a single component is being rendered, thus making key not so useful, it re-initializes the component every time the state selected changes.
But we can have a Wrap Component as a work around, Wrap is given a unique id uid as a prop this can then be used during rendering if state selected points to uid of a Wrap we style it to display: initial else to display: none, this is where key's comes handy we can always render all the siblings and give it a key same as uid
Code:
import {Component} from "react";
function Wrap({uid}) {
return <div>uid = {uid} : <input/></div>
}
export default class CustomReactComponent extends Component {
constructor(props) {
super(props);
this.state = {selected: 0};
this.list = [
<Wrap uid={0}/>,
<Wrap uid={1}/>,
<Wrap uid={2}/>,
<Wrap uid={3}/>
];
}
onSelect = (e) => {
this.setState({selected: parseInt(e.target.value)});
}
render() {
return (
<div>
State:
<select onChange={this.onSelect}>
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<div>
{this.list.map((component) =>
<div key={component.props.uid}
style={{display: component.props.uid !== this.state.selected ? 'none' : 'initial'}}>
{component}
</div>)}
</div>
</div>
);
}
}

How to catch selected text inside input?

I have TextField(material-ui) and I need to catch what text user have selected.
For example I have text: I want to buy car.
User selected: want
How can I catch selected text and generate list component right after the text selection end?
Create an onchange function, and pass event parameter to the onchange function. This event parameter will hold the input element object, on doing event.target.value , you will get selected value. You can replace select with TextField.
function get_data(event){
alert(event.target.value);
}
<select id="data" onchange="get_data(event)">
<option value="one">Option 1 Here</option>
<option value="two">Option 2 Here</option>
</select>
import React, { Component } from 'react'
export default class Select extends Component {
constructor(props){
super(props);
this.state={
value:"",
}
this.handleChange=this.handleChange.bind(this);
}
handleChange(e){
this.setState({
[e.target.name]: e.target.value
})
}
render() {
console.log(this.state.value);
return (
<select id="data" value={this.state.value} name="value" onChange={this.handleChange}>
<option value="one">Option 1 Here</option>
<option value="two">Option 2 Here</option>
</select>
)
}
}
handleChange fire when option is selected! setting state.value to option value
So, basically 2 options
pass the onChange function to your input and get its value from event.target.value
you can use refs to get the DOM node and there you can also get the value of the input (https://reactjs.org/docs/refs-and-the-dom.html#adding-a-ref-to-a-dom-element)

How to automaticaly setState in an onChange event in a select-dropdown, as currently the event is triggered only when the user clicks on the list?

Currently, the default state is empty and is updated only when the user clicks on the drop-down triggering the onChange event. How do I make it so that the item selected by default is also set to the state? So the first option is selected by default in the drop-down, and I'd like to set the first option in the state automatically.
this.state = { slug: '' }
<select>
data && data.map((datum, i) => { return (
<option
value={datum.id}
key={i}
onChange={e => this.setState({ slug: datum.slug })}>
{datum.name}
</option>
)})}
<select>
You can define the default value in the state
this.state = { slug: 'zero' }
and then tells react to set that value in the select
<select value={this.state.slug}>
<option value="zero">Zero</option>
<option value="one">One</option>
<option value="two">Two</option>
</select>
If you want to programmatically select the first value, you can write this.
<select>
data && data.map((datum, i) => {
const isSelected = this.state.slug === '' && i === 0;
return (
<option
selected={isSelected}
value={datum.id}
key={i}
onChange={}
>
{datum.name}
</option>
)})}
</selected>
If you want your state to reflect your default option, you can do that in the constructor of the component.
constructor(props){
super(props);
this.state = {
slug : this.props.data[0].id
}
}
After that you can use one of the solutions I mention before to handling the select component.
If you change data often, you can do that inside componentWillReceiveProps instead of constructor, but the idea it's the same.

How to set placeholder for dropdown in react js?

<select>
<option selected disabled>Select</option
I added an extra option with a disabled property, but this gives me the following warning:
Use the defaultValue or value props on select instead of setting
selected on option.
<select>
{this.props.optionarray.map(function(e){
return <option
value={e[self.props.unique]}
key={e[self.props.unique]}
>
{e[self.props.name]}
</option>
})}
</select>
optionarray is an array which is passed through props to be mapped on each index which has an object and I pass the key through props as well which was in the array. Everything is working here, it's just showing me the warning that I mentioned above.
How do I remove that or how do I set a perfect placeholder for a dropdown in react?
Reason is, React provide a better way of controlling the element by using states, so instead of using selected with option use the value property of select and define its value in the state variable, use the onChange method to update the state, Use it in this way:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '1'
};
}
render(){
return(
<select value={this.state.value} onChange={(e)=>{this.setState({value: e.target.value})}}>
<option value='1' disabled>Select</option>
{
[2,3,4].map((i,j)=>{
return <option key={i} value={i}>{i}</option>
})
}
</select>
);
}
}
How to set placeholder for dropdown?
According to Mozilla Dev Network, placeholder is not a valid attribute on a <select>. So in place of that you can you this also to render a default option:
<option default>Select</option>
Use of key as per DOC:
Keys help React identify which items have changed, are added, or are
removed. Keys should be given to the elements inside the array to give
the elements a stable identity.
Suggestion:
Whenever we create any ui dynamically in loop, we need to assign a unique key to each element, so that react can easily identify the element, it basically used to improve the performance.
Check the working example:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '1'
};
}
render(){
return(
<select value={this.state.value} onChange={(e)=>{this.setState({value: e.target.value})}}>
<option value='1' disabled>Select</option>
{
[2,3,4].map((i,j)=>{
return <option key={i} value={i}>{i}</option>
})
}
</select>
);
}
}
ReactDOM.render(<App />, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='container'/>
Check the fiddle for working example: https://jsfiddle.net/29uk8fn0/
Functional Component
import { useState } from "react";
export default function DropdownComponent(props) {
const [formData, setFromData] = useState({
anyInput: "something",
dropdownInput: null,
});
return (
<>
anyInput: { formData.anyInput }
dropdownInput: { formData.dropdownInput }
<select name={"dropdownName"} onChange={ ({target})=> {
setFromData((prev)=> ({...prev, dropdownInput:target?.value}) );
}
}>
<option key='blankKey' hidden value >Placeholder Text for Select</option>
{
[2,3,4].map((i,j)=>{
return <option key={i} value={i}>{i}</option>
})
}
</select>
</>
)
}

React Bootstrap Validation use validated input in sub component

I am using react-bootstrap-validation that decorates the react-bootstrap Input tag.
The ValidatedInput requires that it is inside a Form component.
When I add my ValidatedInput to a custom sub component I get an error saying it needs to be inside a Form which it is, but I guess it is further down the tree now so can not see the Form.
Is there a way of referencing the parent Form so the ValidatedInput can see the parent.
Looking at the source of the Validation lib I can see that the ValidationInput needs to register to the Form but am not sure how to do this from the sub component.
// Parent render
render(){
<Form
className="fl-form fl-form-inline fl-form-large"
name="customer-details"
onValidSubmit={this._handleValidSubmit}
onInvalidSubmit={this._handleInvalidSubmit}
validationEvent='onChange'>
<TitleSelect handleChange={this.updateDropDown} value={this.state.form.title} />
</form>
}
// Sub class containing the ValidatedInput
export class TitleSelect extends React.Component {
static propTypes = {
handleChange: React.PropTypes.func.isRequired,
value: React.PropTypes.string.isRequired
}
render(){
return (
<ValidatedInput
name="title"
label='title'
type='select'
value={this.props.value}
onChange={this.props.handleChange}
groupClassName='form-group input-title'
wrapperClassName='fl-input-wrapper'
validate='required'
errorHelp={{
required: 'Please select a title.'
}}>
<option value="" ></option>
<option value="Mr">Mr</option>
<option value="Mrs">Mrs</option>
<option value="Master">Mstr.</option>
<option value="Ms">Ms</option>
<option value="Miss">Miss</option>
<option value="Reverend">Rev.</option>
<option value="Doctor">Dr.</option>
<option value="Professor">Prof.</option>
<option value="Lord">Lord</option>
<option value="Lady">Lady</option>
<option value="Sir">Sir</option>
<option value="Master">Mstr.</option>
<option value="Miss">Miss</option>
</ValidatedInput>
)
}
};
At the moment this is impossible to do. It will be possible in a future release once we get proper parent-based contexts in react and I will migrate the component to contexts. But for now I would recommend to split your render() method to couple of smaller ones and reuse them.
Sa #Ваня Решетников said above it's impossible to do it now because of limitations of current design. A solution I went for is this.
Convert subcomponent to plain JS object
TitleSelect = {
// move prop types to parent
renderTitleSelect(){
...
}
}
Add new object as a mixin to parent and render a function
mixins: [TitleSelect],
...
render() {
<Form ...>
// parentheses are important!
{this.renderTitleSelect()}
</Form>
}

Resources